Created
January 14, 2020 16:52
-
-
Save dsafreno/8f654b10836b98cd5592785a0c8cb705 to your computer and use it in GitHub Desktop.
React Hooks for loading Firebase Data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useReducer, useEffect, useRef } from 'react'; | |
import firebase from 'firebase/app'; | |
import equal from 'deep-equal'; | |
function filterKeys(raw, allowed) { | |
if (!raw) { | |
return raw; | |
} | |
let s = new Set(allowed); | |
return Object.keys(raw) | |
.filter(key => s.has(key)) | |
.reduce((obj, key) => { | |
obj[key] = raw[key]; | |
return obj; | |
}, {}); | |
} | |
export const useDbData = (paths) => { | |
let unsubscribes = useRef({}) | |
let [data, dispatch] = useReducer((d, action) => { | |
let {type, path, payload} = action | |
switch (type) { | |
case 'upsert': | |
if (payload) { | |
return Object.assign({}, d, {[path]: payload}) | |
} else { | |
let newData = Object.assign({}, d) | |
delete newData[path] | |
return newData | |
} | |
default: | |
throw new Error('bad type to reducer', type) | |
} | |
}, {}) | |
useEffect(() => { | |
for (let path of Object.keys(paths)) { | |
if (unsubscribes.current.hasOwnProperty(path)) { | |
continue | |
} | |
let ref = firebase.database().ref(path) | |
let lastVal = undefined | |
let f = ref.on('value', snap => { | |
let val = snap.val() | |
val = paths[path] ? filterKeys(val, paths[path]) : val | |
if (!equal(val, lastVal)) { | |
dispatch({type: 'upsert', payload: val, path}) | |
lastVal = val | |
} | |
}) | |
unsubscribes.current[path] = () => ref.off('value', f) | |
} | |
let pathSet = new Set(Object.keys(paths)) | |
for (let path of Object.keys(unsubscribes.current)) { | |
if (!pathSet.has(path)) { | |
unsubscribes.current[path]() | |
delete unsubscribes.current[path] | |
dispatch({type: 'upsert', path}) | |
} | |
} | |
}) | |
useEffect(() => { | |
return () => { | |
for (let unsubscribe of Object.values(unsubscribes.current)) { | |
unsubscribe() | |
} | |
} | |
}, []) | |
return data | |
} | |
export const useDbDatum = (path, allowed=null) => { | |
let datum = useDbData(path ? {[path]: allowed} : {}) | |
if (datum[path]) { | |
return datum[path] | |
} | |
return null | |
} |
Smart code!
You use Object.keys(paths)
so paths should be an object.
Yet in your demo code at https://pragli.com/blog/firebase-as-a-react-hook/, you show:
let paths = uids.map(id =>
students/${id}/name);
indicating that paths is an array.
I think you mean it to be an object since paths[path] can hold the allowed keys
Possible optimization to avoid the expensive deep-equal if you don't have 'allowed' keys i.e. if you want to return the whole object, since Firebase only sends a value event when anything in the path has changed.
if (paths[path]) {
val = filterKeys(val, paths[path]);
if (!equal(val, lastVal)) {
dispatch({ type: 'upsert', payload: val, path });
lastVal = val;
}
}
else {
// always dispatch since Firebase only has 'value' event if it changed
dispatch({ type: 'upsert', payload: val, path });
lastVal = val;
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Awesome! Could do a version for Firestore too