-
-
Save zomars/0fa63604baf12517c5b7b592c1c72397 to your computer and use it in GitHub Desktop.
Hook version of dsafreno's withDbData for RealTime Database subscribing in React.
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 { useEffect, useRef, useState } from 'react'; | |
import firebase from 'firebase/app'; | |
import equal from 'deep-equal'; | |
function filterKeys(raw, allowed) { | |
if (!raw) { | |
return raw; | |
} | |
const s = new Set(allowed); | |
return Object.keys(raw) | |
.filter(key => s.has(key)) | |
.reduce((obj, key) => { | |
const newObj = obj; | |
newObj[key] = raw[key]; | |
return newObj; | |
}, {}); | |
} | |
export default function useRtDbData(specs = []) { | |
const isInitialMount = useRef(true); | |
const unmounting = useRef(false); | |
const offs = useRef({}); | |
const [state, setState] = useState({}); | |
const subscribeToSpec = spec => { | |
const { name, keys, path } = spec; | |
if (!path) { | |
return; | |
} | |
const ref = firebase.database().ref(path); | |
const offFunc = ref.on('value', snap => { | |
const dat = keys ? filterKeys(snap.val(), keys) : snap.val(); | |
if (equal(dat, state[name])) { | |
return; | |
} | |
setState({ | |
[name]: dat, | |
}); | |
}); | |
let hasBeenOffed = false; | |
const off = () => { | |
if (hasBeenOffed) { | |
return; | |
} | |
hasBeenOffed = true; | |
if (!unmounting.current) { | |
setState({ | |
[name]: null, | |
}); | |
} | |
ref.off('value', offFunc); | |
}; | |
if (!offs.current[path]) { | |
offs.current[path] = null; | |
} | |
offs.current[path] = off; | |
}; | |
useEffect(() => { | |
if (isInitialMount.current) { | |
isInitialMount.current = false; | |
specs.forEach(spec => { | |
subscribeToSpec(spec); | |
}); | |
} else { | |
const resubs = new Set(); | |
specs.forEach(spec => { | |
const { path } = spec; | |
if (typeof offs.current[path] === 'function') { | |
offs.current[path](); | |
} | |
offs.current[path] = null; | |
if (resubs.has(spec.name)) return; | |
resubs.add(spec.name); | |
subscribeToSpec(spec); | |
}); | |
} | |
return () => { | |
unmounting.current = true; | |
Object.values(offs.current).forEach(offFunc => { | |
offFunc(); | |
}); | |
offs.current = {}; | |
}; | |
}, [specs]); | |
const isLoading = specs.some(spec => !state[spec.name]); | |
return { ...state, isLoading }; | |
} |
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 from 'react'; | |
import useRtDbData from './useRtDbData'; | |
const UserItemExample = () => { | |
const userId = 15; | |
const USERS_SPEC = { | |
name: 'user', | |
path: `users/${userId}`, | |
}; | |
const { user, isLoading } = useRtDbData([USERS_SPEC]); | |
console.log('user', user); | |
if (isLoading) return null; | |
return <span>{user.name}</span>; | |
}; | |
export default UserItemExample; |
Removed await
since the dev can decide wether to show or not a loading indicator if the data has not been loaded yet.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated so there's no need to define and parse a template since we can pass a dynamic path and the hook will update accordingly.