Skip to content

Commit c935b2e

Browse files
committed
Consolidate ref comparison hooks
1 parent 266deb6 commit c935b2e

File tree

7 files changed

+83
-78
lines changed

7 files changed

+83
-78
lines changed

database/useList.ts

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { database } from 'firebase';
2-
import { useEffect, useReducer, useRef, useState } from 'react';
2+
import { useEffect, useReducer } from 'react';
3+
import { useIsEqualRef } from '../util';
34

45
export type ListHook = {
56
error?: Object;
6-
list: database.DataSnapshot[];
77
loading: boolean;
8+
value: database.DataSnapshot[];
89
};
910

1011
type KeyValueState = {
@@ -112,18 +113,7 @@ const reducer = (state: ReducerState, action: ReducerAction): ReducerState => {
112113
export default (query: database.Query): ListHook => {
113114
const [state, dispatch] = useReducer(reducer, initialState);
114115

115-
const [error, setError] = useState(false);
116-
const [loading, setLoading] = useState(true);
117-
// Combine keys and values in a single state hook to allow them to be manipulated together
118-
const [{ values }, setKeysValues] = useState({ keys: [], values: [] });
119-
// Set a ref for the query to make sure that `useEffect` doesn't run
120-
// every time this renders
121-
const queryRef = useRef(query);
122-
// If the query has changed, then reset the state
123-
if (!query.isEqual(queryRef.current)) {
124-
queryRef.current = query;
125-
dispatch({ type: 'reset' });
126-
}
116+
const ref = useIsEqualRef(query, () => dispatch({ type: 'reset' }));
127117

128118
const onChildAdded = (
129119
snapshot: database.DataSnapshot | null,
@@ -149,16 +139,15 @@ export default (query: database.Query): ListHook => {
149139

150140
useEffect(
151141
() => {
152-
const query: database.Query = queryRef.current;
142+
const query: database.Query = ref.current;
153143
// This is here to indicate that all the data has been successfully received
154144
query.once(
155145
'value',
156146
() => {
157-
setLoading(false);
147+
dispatch({ type: 'loading ' });
158148
},
159-
(err: object) => {
160-
setError(err);
161-
setLoading(false);
149+
(error: object) => {
150+
dispatch({ type: 'error', error });
162151
}
163152
);
164153
query.on('child_added', onChildAdded);
@@ -173,13 +162,13 @@ export default (query: database.Query): ListHook => {
173162
query.off('child_removed', onChildRemoved);
174163
};
175164
},
176-
[queryRef.current]
165+
[ref.current]
177166
);
178167

179168
return {
180-
error,
181-
list: values,
182-
loading,
169+
error: state.error,
170+
loading: state.loading,
171+
value: state.value,
183172
};
184173
};
185174

database/useObject.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { database } from 'firebase';
2-
import { useEffect, useRef } from 'react';
3-
import { useLoadingValue } from '../util';
2+
import { useEffect } from 'react';
3+
import { useIsEqualRef, useLoadingValue } from '../util';
44

55
export type ObjectHook = {
66
error?: object;
@@ -12,25 +12,18 @@ export default (query: database.Query): ObjectHook => {
1212
const { error, loading, reset, setError, setValue, value } = useLoadingValue<
1313
database.DataSnapshot
1414
>();
15-
// Set a ref for the query to make sure that `useEffect` doesn't run
16-
// every time this renders
17-
const queryRef = useRef(query);
18-
// If the query has changed, then
19-
if (!query.isEqual(queryRef.current)) {
20-
queryRef.current = query;
21-
reset();
22-
}
15+
const ref = useIsEqualRef(query, reset);
2316

2417
useEffect(
2518
() => {
26-
const query: database.Query = queryRef.current;
19+
const query = ref.current;
2720
query.on('value', setValue, setError);
2821

2922
return () => {
3023
query.off('value', setValue);
3124
};
3225
},
33-
[queryRef.current]
26+
[ref.current]
3427
);
3528

3629
return {

firestore/useCollection.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { firestore } from 'firebase';
2-
import { useEffect, useRef } from 'react';
3-
import { useLoadingValue } from '../util';
2+
import { useEffect } from 'react';
3+
import { useIsEqualRef, useLoadingValue } from '../util';
44

55
export type CollectionHook = {
66
error?: object;
@@ -15,26 +15,19 @@ export default (
1515
const { error, loading, reset, setError, setValue, value } = useLoadingValue<
1616
firestore.QuerySnapshot
1717
>();
18-
// Set a ref for the query to make sure that `useEffect` doesn't run
19-
// every time this renders
20-
const queryRef = useRef(query);
21-
// If the query has changed, then
22-
if (!query.isEqual(queryRef.current)) {
23-
queryRef.current = query;
24-
reset();
25-
}
18+
const ref = useIsEqualRef(query, reset);
2619

2720
useEffect(
2821
() => {
2922
const listener = options
30-
? queryRef.current.onSnapshot(options, setValue, setError)
31-
: queryRef.current.onSnapshot(setValue, setError);
23+
? ref.current.onSnapshot(options, setValue, setError)
24+
: ref.current.onSnapshot(setValue, setError);
3225

3326
return () => {
3427
listener();
3528
};
3629
},
37-
[queryRef.current]
30+
[ref.current]
3831
);
3932

4033
return {

firestore/useDocument.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { firestore } from 'firebase';
2-
import { useEffect, useRef } from 'react';
3-
import { useLoadingValue } from '../util';
2+
import { useEffect } from 'react';
3+
import { useIsEqualRef, useLoadingValue } from '../util';
44

55
export type DocumentHook = {
66
error?: object;
@@ -9,33 +9,25 @@ export type DocumentHook = {
99
};
1010

1111
export default (
12-
doc: firestore.DocumentReference,
12+
docRef: firestore.DocumentReference,
1313
options?: firestore.SnapshotListenOptions
1414
): DocumentHook => {
1515
const { error, loading, reset, setError, setValue, value } = useLoadingValue<
1616
firestore.DocumentSnapshot
1717
>();
18-
// Set a ref for the query to make sure that `useEffect` doesn't run
19-
// every time this renders
20-
const docRef = useRef(doc);
21-
// If the query has changed, then
22-
if (!doc.isEqual(docRef.current)) {
23-
docRef.current = doc;
24-
reset();
25-
}
18+
const ref = useIsEqualRef(docRef, reset);
2619

2720
useEffect(
2821
() => {
2922
const listener = options
30-
? docRef.current.onSnapshot(options, setValue, setError)
31-
: docRef.current.onSnapshot(setValue, setError);
23+
? ref.current.onSnapshot(options, setValue, setError)
24+
: ref.current.onSnapshot(setValue, setError);
3225

3326
return () => {
3427
listener();
3528
};
3629
},
37-
// TODO: Check if this works suitably for 'ref' parameters
38-
[docRef.current]
30+
[ref.current]
3931
);
4032

4133
return {

storage/useDownloadURL.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,36 @@
11
import { storage } from 'firebase';
2-
import { useEffect, useRef } from 'react';
3-
import { useLoadingValue } from '../util';
2+
import { useEffect } from 'react';
3+
import { useComparatorRef, useLoadingValue } from '../util';
44

55
export type DownloadURLHook = {
66
error?: object;
77
loading: boolean;
8-
url?: string;
8+
value?: string;
99
};
1010

11-
export default (ref: storage.Reference): DownloadURLHook => {
11+
export default (storageRef: storage.Reference): DownloadURLHook => {
1212
const { error, loading, reset, setError, setValue, value } = useLoadingValue<
1313
string
1414
>();
15-
// Set a ref for the query to make sure that `useEffect` doesn't run
16-
// every time this renders
17-
const refRef = useRef(ref);
18-
// If the query has changed, then
19-
if (ref.fullPath !== refRef.current.fullPath) {
20-
refRef.current = ref;
21-
reset();
22-
}
15+
const ref = useComparatorRef(
16+
storageRef,
17+
(v1, v2) => v1.fullPath === v2.fullPath,
18+
reset
19+
);
2320

2421
useEffect(
2522
() => {
26-
refRef.current
23+
ref.current
2724
.getDownloadURL()
2825
.then(setValue)
2926
.catch(setError);
3027
},
31-
[refRef.current]
28+
[ref.current]
3229
);
3330

3431
return {
3532
error,
3633
loading,
37-
url: value,
34+
value,
3835
};
3936
};

util/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { default as useLoadingValue } from './useLoadingValue';
2+
export * from './refHooks';

util/refHooks.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
type RefHook<T> = {
4+
current: T;
5+
};
6+
7+
export const useComparatorRef = <T>(
8+
value: T,
9+
isEqual: (v1: T, v2: T) => boolean,
10+
onChange?: () => void
11+
): RefHook<T> => {
12+
const ref = useRef(value);
13+
useEffect(() => {
14+
if (!isEqual(value, ref.current)) {
15+
ref.current = value;
16+
if (onChange) {
17+
onChange();
18+
}
19+
}
20+
});
21+
return ref;
22+
};
23+
24+
export interface HasIsEqual<T> {
25+
isEqual: (value: T) => boolean;
26+
}
27+
28+
export const useIsEqualRef = <T extends HasIsEqual<T>>(
29+
value: T,
30+
onChange?: () => void
31+
): RefHook<T> => {
32+
return useComparatorRef(value, (v1, v2) => v1.isEqual(v2), onChange);
33+
};
34+
35+
export const useIdentifyRef = <T>(
36+
value: T,
37+
onChange?: () => void
38+
): RefHook<T> => {
39+
return useComparatorRef(value, (v1, v2) => v1 === v2, onChange);
40+
};

0 commit comments

Comments
 (0)