Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
React Hooks for loading Firebase Data
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
}
@alvinthen

This comment has been minimized.

Copy link

@alvinthen alvinthen commented Jan 16, 2020

Awesome! Could do a version for Firestore too

@bayareacoder

This comment has been minimized.

Copy link

@bayareacoder bayareacoder commented Mar 4, 2020

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
You can’t perform that action at this time.