Skip to content

Commit a1f2b64

Browse files
authored
Merge pull request #90 from caleb-harrelson/useList-efficiency
Improve useList efficiency
2 parents f7bc47e + 225aa0d commit a1f2b64

File tree

3 files changed

+113
-46
lines changed

3 files changed

+113
-46
lines changed

database/helpers/useListReducer.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type RemoveAction = {
3333
snapshot: firebase.database.DataSnapshot | null;
3434
};
3535
type ResetAction = { type: 'reset' };
36-
type ValueAction = { type: 'value' };
36+
type ValueAction = { type: 'value'; snapshots: firebase.database.DataSnapshot[] | null };
3737
type ReducerAction =
3838
| AddAction
3939
| ChangeAction
@@ -110,6 +110,7 @@ const listReducer = (
110110
...state,
111111
error: undefined,
112112
loading: false,
113+
value: setValue(action.snapshots),
113114
};
114115
case 'empty':
115116
return {
@@ -125,6 +126,30 @@ const listReducer = (
125126
}
126127
};
127128

129+
const setValue = (snapshots: firebase.database.DataSnapshot[] | null): KeyValueState => {
130+
if (!snapshots) {
131+
return {
132+
keys: [],
133+
values: [],
134+
};
135+
}
136+
137+
const keys: string[] = [];
138+
const values: firebase.database.DataSnapshot[] = [];
139+
snapshots.forEach((snapshot) => {
140+
if (!snapshot.key) {
141+
return;
142+
}
143+
keys.push(snapshot.key);
144+
values.push(snapshot);
145+
});
146+
147+
return {
148+
keys,
149+
values,
150+
};
151+
};
152+
128153
const addChild = (
129154
currentState: KeyValueState,
130155
snapshot: firebase.database.DataSnapshot,

database/useList.ts

Lines changed: 86 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,58 +14,100 @@ export type ListValsHook<T> = LoadingHook<T[], firebase.FirebaseError>;
1414
export const useList = (query?: firebase.database.Query | null): ListHook => {
1515
const [state, dispatch] = useListReducer();
1616

17-
const ref = useIsEqualRef(query, () => dispatch({ type: 'reset' }));
18-
19-
const onChildAdded = (
20-
snapshot: firebase.database.DataSnapshot | null,
21-
previousKey?: string | null
22-
) => {
23-
dispatch({ type: 'add', previousKey, snapshot });
24-
};
25-
26-
const onChildChanged = (snapshot: firebase.database.DataSnapshot | null) => {
27-
dispatch({ type: 'change', snapshot });
28-
};
29-
30-
const onChildMoved = (
31-
snapshot: firebase.database.DataSnapshot | null,
32-
previousKey?: string | null
33-
) => {
34-
dispatch({ type: 'move', previousKey, snapshot });
35-
};
36-
37-
const onChildRemoved = (snapshot: firebase.database.DataSnapshot | null) => {
38-
dispatch({ type: 'remove', snapshot });
39-
};
40-
41-
const onError = (error: firebase.FirebaseError) => {
42-
dispatch({ type: 'error', error });
43-
};
44-
45-
const onValue = () => {
46-
dispatch({ type: 'value' });
47-
};
17+
const queryRef = useIsEqualRef(query, () => dispatch({ type: 'reset' }));
18+
const ref: firebase.database.Query | null | undefined = queryRef.current;
4819

4920
useEffect(() => {
50-
const query: firebase.database.Query | null | undefined = ref.current;
51-
if (!query) {
21+
if (!ref) {
5222
dispatch({ type: 'empty' });
5323
return;
5424
}
55-
// This is here to indicate that all the data has been successfully received
56-
query.once('value', onValue, onError);
57-
query.on('child_added', onChildAdded, onError);
58-
query.on('child_changed', onChildChanged, onError);
59-
query.on('child_moved', onChildMoved, onError);
60-
query.on('child_removed', onChildRemoved, onError);
25+
26+
const onChildAdded = (
27+
snapshot: firebase.database.DataSnapshot | null,
28+
previousKey?: string | null
29+
) => {
30+
dispatch({ type: 'add', previousKey, snapshot });
31+
};
32+
33+
const onChildChanged = (
34+
snapshot: firebase.database.DataSnapshot | null
35+
) => {
36+
dispatch({ type: 'change', snapshot });
37+
};
38+
39+
const onChildMoved = (
40+
snapshot: firebase.database.DataSnapshot | null,
41+
previousKey?: string | null
42+
) => {
43+
dispatch({ type: 'move', previousKey, snapshot });
44+
};
45+
46+
const onChildRemoved = (
47+
snapshot: firebase.database.DataSnapshot | null
48+
) => {
49+
dispatch({ type: 'remove', snapshot });
50+
};
51+
52+
const onError = (error: firebase.FirebaseError) => {
53+
dispatch({ type: 'error', error });
54+
};
55+
56+
const onValue = (snapshots: firebase.database.DataSnapshot[] | null) => {
57+
dispatch({ type: 'value', snapshots });
58+
};
59+
60+
let childAddedHandler: ReturnType<typeof ref.on> | undefined;
61+
const children: firebase.database.DataSnapshot[] = [];
62+
const onInitialLoad = (snapshot: firebase.database.DataSnapshot) => {
63+
let childrenToProcess = Object.keys(snapshot.val()).length;
64+
65+
const onChildAddedWithoutInitialLoad = (
66+
addedChild: firebase.database.DataSnapshot,
67+
previousKey?: string | null
68+
) => {
69+
// process the first batch of children all at once
70+
if (childrenToProcess > 0) {
71+
childrenToProcess--;
72+
children.push(addedChild);
73+
74+
if (childrenToProcess === 0) {
75+
onValue(children);
76+
}
77+
78+
return;
79+
}
80+
81+
onChildAdded(snapshot, previousKey);
82+
};
83+
84+
childAddedHandler = ref.on(
85+
'child_added',
86+
onChildAddedWithoutInitialLoad,
87+
onError
88+
);
89+
};
90+
91+
ref.once('value', onInitialLoad, onError);
92+
const childChangedHandler = ref.on(
93+
'child_changed',
94+
onChildChanged,
95+
onError
96+
);
97+
const childMovedHandler = ref.on('child_moved', onChildMoved, onError);
98+
const childRemovedHandler = ref.on(
99+
'child_removed',
100+
onChildRemoved,
101+
onError
102+
);
61103

62104
return () => {
63-
query.off('child_added', onChildAdded);
64-
query.off('child_changed', onChildChanged);
65-
query.off('child_moved', onChildMoved);
66-
query.off('child_removed', onChildRemoved);
105+
ref.off('child_added', childAddedHandler);
106+
ref.off('child_changed', childChangedHandler);
107+
ref.off('child_moved', childMovedHandler);
108+
ref.off('child_removed', childRemovedHandler);
67109
};
68-
}, [ref.current]);
110+
}, [dispatch, ref]);
69111

70112
const resArray: ListHook = [state.value.values, state.loading, state.error];
71113
return useMemo(() => resArray, resArray);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"typescript": "2.8.1"
6464
},
6565
"peerDependencies": {
66-
"react": "^16.8.0 || ^17.0.0"
66+
"react": ">= 16.8.0"
6767
},
6868
"typings": "index.d.ts"
6969
}

0 commit comments

Comments
 (0)