-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdb.js
167 lines (140 loc) · 4.72 KB
/
db.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import { useReducer, useEffect, useRef } from "react";
import firebase from "./firebase";
const firestore = firebase.firestore();
const serverTimestamp = firebase.firestore.FieldValue.serverTimestamp;
/**** USERS ****/
// Fetch user data (hook)
// This is called automatically by auth.js and merged into auth.user
export function useUser(uid) {
return useQuery(uid && firestore.collection("users").doc(uid));
}
// Update an existing user
export function updateUser(uid, data) {
return firestore.collection("users").doc(uid).update(data);
}
// Create a new user
export function createUser(uid, data) {
return firestore
.collection("users")
.doc(uid)
.set({ uid, ...data }, { merge: true });
}
/**** ITEMS ****/
/* Example query functions (modify to your needs) */
// Fetch all items by owner (hook)
export function useItemsByOwner(owner) {
return useQuery(
owner &&
firestore
.collection("items")
.where("owner", "==", owner)
.orderBy("createdAt", "desc")
);
}
// Fetch item data
export function useItem(id) {
return useQuery(id && firestore.collection("items").doc(id));
}
// Update an item
export function updateItem(id, data) {
return firestore.collection("items").doc(id).update(data);
}
// Create a new item
export function createItem(data) {
return firestore.collection("items").add({
...data,
createdAt: serverTimestamp(),
});
}
// Delete an item
export function deleteItem(id) {
return firestore.collection("items").doc(id).delete();
}
/**** HELPERS ****/
// Reducer for useQuery hook state and actions
const reducer = (state, action) => {
switch (action.type) {
case "idle":
return { status: "idle", data: undefined, error: undefined };
case "loading":
return { status: "loading", data: undefined, error: undefined };
case "success":
return { status: "success", data: action.payload, error: undefined };
case "error":
return { status: "error", data: undefined, error: action.payload };
default:
throw new Error("invalid action");
}
};
// Custom React hook that subscribes to a Firestore query
function useQuery(query) {
// Our initial state
// Start with an "idle" status if query is falsy, as that means hook consumer is
// waiting on required data before creating the query object.
// Example: useQuery(uid && firestore.collection("profiles").doc(uid))
const initialState = {
status: query ? "loading" : "idle",
data: undefined,
error: undefined,
};
// Setup our state and actions
const [state, dispatch] = useReducer(reducer, initialState);
// Gives us previous query object if query is the same, ensuring
// we don't trigger useEffect on every render due to query technically
// being a new object reference on every render.
const queryCached = useMemoCompare(query, (prevQuery) => {
// Use built-in Firestore isEqual method to determine if "equal"
return prevQuery && query && query.isEqual(prevQuery);
});
useEffect(() => {
// Return early if query is falsy and reset to "idle" status in case
// we're coming from "success" or "error" status due to query change.
if (!queryCached) {
dispatch({ type: "idle" });
return;
}
dispatch({ type: "loading" });
// Subscribe to query with onSnapshot
// Will unsubscribe on cleanup since this returns an unsubscribe function
return queryCached.onSnapshot(
(response) => {
// Get data for collection or doc
const data = response.docs
? getCollectionData(response)
: getDocData(response);
dispatch({ type: "success", payload: data });
},
(error) => {
dispatch({ type: "error", payload: error });
}
);
}, [queryCached]); // Only run effect if queryCached changes
return state;
}
// Get doc data and merge doc.id
function getDocData(doc) {
return doc.exists === true ? { id: doc.id, ...doc.data() } : null;
}
// Get array of doc data from collection
function getCollectionData(collection) {
return collection.docs.map(getDocData);
}
// Used by useQuery to store Firestore query object reference
function useMemoCompare(next, compare) {
// Ref for storing previous value
const previousRef = useRef();
const previous = previousRef.current;
// Pass previous and next value to compare function
// to determine whether to consider them equal.
const isEqual = compare(previous, next);
// If not equal update previousRef to next value.
// We only update if not equal so that this hook continues to return
// the same old value if compare keeps returning true.
useEffect(() => {
if (!isEqual) {
previousRef.current = next;
}
});
// Finally, if equal then return the previous value
return isEqual ? previous : next;
}