11import { database } from 'firebase' ;
2- import { useEffect , useRef , useState } from 'react' ;
2+ import { useEffect , useReducer , useRef , useState } from 'react' ;
33
44export type ListHook = {
55 error ?: Object ;
@@ -12,72 +12,139 @@ type KeyValueState = {
1212 values : database . DataSnapshot [ ] ;
1313} ;
1414
15+ type ReducerState = {
16+ error ?: object ;
17+ loading : boolean ;
18+ value : KeyValueState ;
19+ } ;
20+
21+ type AddAction = {
22+ type : 'add' ;
23+ previousKey ?: string | null ;
24+ snapshot : database . DataSnapshot | null ;
25+ } ;
26+ type ChangeAction = {
27+ type : 'change' ;
28+ snapshot : database . DataSnapshot | null ;
29+ } ;
30+ type ErrorAction = { type : 'error' ; error : object } ;
31+ type MoveAction = {
32+ type : 'move' ;
33+ previousKey ?: string | null ;
34+ snapshot : database . DataSnapshot | null ;
35+ } ;
36+ type RemoveAction = {
37+ type : 'remove' ;
38+ snapshot : database . DataSnapshot | null ;
39+ } ;
40+ type ResetAction = { type : 'reset' } ;
41+ type ValueAction = { type : 'value' ; value : any } ;
42+ type ReducerAction =
43+ | AddAction
44+ | ChangeAction
45+ | ErrorAction
46+ | MoveAction
47+ | RemoveAction
48+ | ResetAction
49+ | ValueAction ;
50+
51+ const initialState : ReducerState = {
52+ loading : true ,
53+ value : {
54+ keys : [ ] ,
55+ values : [ ] ,
56+ } ,
57+ } ;
58+
59+ const reducer = ( state : ReducerState , action : ReducerAction ) : ReducerState => {
60+ switch ( action . type ) {
61+ case 'add' :
62+ if ( ! action . snapshot ) {
63+ return state ;
64+ }
65+ return {
66+ ...state ,
67+ value : addChild ( state . value , action . snapshot , action . previousKey ) ,
68+ } ;
69+ case 'change' :
70+ if ( ! action . snapshot ) {
71+ return state ;
72+ }
73+ return {
74+ ...state ,
75+ value : changeChild ( state . value , action . snapshot ) ,
76+ } ;
77+ case 'error' :
78+ return {
79+ ...state ,
80+ error : action . error ,
81+ loading : false ,
82+ } ;
83+ case 'move' :
84+ if ( ! action . snapshot ) {
85+ return state ;
86+ }
87+ return {
88+ ...state ,
89+ value : moveChild ( state . value , action . snapshot , action . previousKey ) ,
90+ } ;
91+ case 'remove' :
92+ if ( ! action . snapshot ) {
93+ return state ;
94+ }
95+ return {
96+ ...state ,
97+ value : removeChild ( state . value , action . snapshot ) ,
98+ } ;
99+ case 'reset' :
100+ return initialState ;
101+ case 'value' :
102+ return {
103+ ...state ,
104+ loading : false ,
105+ value : action . value ,
106+ } ;
107+ default :
108+ return state ;
109+ }
110+ } ;
111+
15112export default ( query : database . Query ) : ListHook => {
113+ const [ state , dispatch ] = useReducer ( reducer , initialState ) ;
114+
16115 const [ error , setError ] = useState ( false ) ;
17116 const [ loading , setLoading ] = useState ( true ) ;
18117 // Combine keys and values in a single state hook to allow them to be manipulated together
19118 const [ { values } , setKeysValues ] = useState ( { keys : [ ] , values : [ ] } ) ;
20119 // Set a ref for the query to make sure that `useEffect` doesn't run
21120 // every time this renders
22121 const queryRef = useRef ( query ) ;
23- // If the query has changed, then
122+ // If the query has changed, then reset the state
24123 if ( ! query . isEqual ( queryRef . current ) ) {
25124 queryRef . current = query ;
26- setError ( false ) ;
27- setLoading ( true ) ;
28- setKeysValues ( { keys : [ ] , values : [ ] } ) ;
125+ dispatch ( { type : 'reset' } ) ;
29126 }
30127
31128 const onChildAdded = (
32129 snapshot : database . DataSnapshot | null ,
33130 previousKey ?: string | null
34131 ) => {
35- setKeysValues ( ( prevKeyValueState : KeyValueState ) => {
36- return snapshot
37- ? addChild ( prevKeyValueState , snapshot , previousKey )
38- : prevKeyValueState ;
39- } ) ;
132+ dispatch ( { type : 'add' , previousKey, snapshot } ) ;
40133 } ;
41134
42135 const onChildChanged = ( snapshot : database . DataSnapshot | null ) => {
43- setKeysValues ( ( prevKeyValueState : KeyValueState ) => {
44- if ( ! snapshot || ! snapshot . key ) {
45- return prevKeyValueState ;
46- }
47-
48- const index = prevKeyValueState . keys . indexOf ( snapshot . key ) ;
49- return {
50- ...prevKeyValueState ,
51- values : [
52- ...prevKeyValueState . values . slice ( 0 , index ) ,
53- snapshot ,
54- ...prevKeyValueState . values . slice ( index + 1 ) ,
55- ] ,
56- } ;
57- } ) ;
136+ dispatch ( { type : 'change' , snapshot } ) ;
58137 } ;
59138
60139 const onChildMoved = (
61140 snapshot : database . DataSnapshot | null ,
62141 previousKey ?: string | null
63142 ) => {
64- setKeysValues ( ( prevKeyValueState : KeyValueState ) => {
65- if ( ! snapshot ) {
66- return prevKeyValueState ;
67- }
68- // Remove the child from it's previous location
69- const tempKeyValueState = removeChild ( prevKeyValueState , snapshot ) ;
70- // Add the child into it's new location
71- return addChild ( tempKeyValueState , snapshot , previousKey ) ;
72- } ) ;
143+ dispatch ( { type : 'move' , previousKey, snapshot } ) ;
73144 } ;
74145
75146 const onChildRemoved = ( snapshot : database . DataSnapshot | null ) => {
76- setKeysValues ( ( prevKeyValueState : KeyValueState ) => {
77- return snapshot
78- ? removeChild ( prevKeyValueState , snapshot )
79- : prevKeyValueState ;
80- } ) ;
147+ dispatch ( { type : 'remove' , snapshot } ) ;
81148 } ;
82149
83150 useEffect (
@@ -117,15 +184,15 @@ export default (query: database.Query): ListHook => {
117184} ;
118185
119186const addChild = (
120- keyValueState : KeyValueState ,
187+ currentState : KeyValueState ,
121188 snapshot : firebase . database . DataSnapshot ,
122189 previousKey ?: string | null
123190) : KeyValueState => {
124191 if ( ! snapshot . key ) {
125- return keyValueState ;
192+ return currentState ;
126193 }
127194
128- const { keys, values } = keyValueState ;
195+ const { keys, values } = currentState ;
129196 if ( ! previousKey ) {
130197 // The child has been added to the start of the list
131198 return {
@@ -146,18 +213,47 @@ const addChild = (
146213 } ;
147214} ;
148215
216+ const changeChild = (
217+ currentState : KeyValueState ,
218+ snapshot : firebase . database . DataSnapshot
219+ ) : KeyValueState => {
220+ if ( ! snapshot . key ) {
221+ return currentState ;
222+ }
223+ const index = currentState . keys . indexOf ( snapshot . key ) ;
224+ return {
225+ ...currentState ,
226+ values : [
227+ ...currentState . values . slice ( 0 , index ) ,
228+ snapshot ,
229+ ...currentState . values . slice ( index + 1 ) ,
230+ ] ,
231+ } ;
232+ } ;
233+
149234const removeChild = (
150- keyValueState : KeyValueState ,
235+ currentState : KeyValueState ,
151236 snapshot : firebase . database . DataSnapshot
152237) : KeyValueState => {
153238 if ( ! snapshot . key ) {
154- return keyValueState ;
239+ return currentState ;
155240 }
156241
157- const { keys, values } = keyValueState ;
242+ const { keys, values } = currentState ;
158243 const index = keys . indexOf ( snapshot . key ) ;
159244 return {
160245 keys : [ ...keys . slice ( 0 , index ) , ...keys . slice ( index + 1 ) ] ,
161246 values : [ ...values . slice ( 0 , index ) , ...values . slice ( index + 1 ) ] ,
162247 } ;
163248} ;
249+
250+ const moveChild = (
251+ currentState : KeyValueState ,
252+ snapshot : firebase . database . DataSnapshot ,
253+ previousKey ?: string | null
254+ ) : KeyValueState => {
255+ // Remove the child from it's previous location
256+ const tempValue = removeChild ( currentState , snapshot ) ;
257+ // Add the child into it's new location
258+ return addChild ( tempValue , snapshot , previousKey ) ;
259+ } ;
0 commit comments