Last active
July 21, 2020 17:14
-
-
Save dsafreno/43850a4333b4a56a57f4e64d730ccc8b to your computer and use it in GitHub Desktop.
Helper to use Firebase RTDB with React more conveniently. From https://pragli.com/blog/how-we-use-firebase-instead-of-redux-with-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 React 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; | |
}, {}); | |
} | |
function parseSpec(spec) { | |
let {template} = spec; | |
let starts = template.split('{') | |
starts.shift(); | |
let propIds = []; | |
for (let start of starts) { | |
propIds.push(start.split('}')[0]); | |
} | |
let formatPath = (props) => { | |
let vals = props; | |
let path = template; | |
for (let key of Object.keys(vals)) { | |
if (!vals[key] && path.includes(`{${key}}`)) { | |
return null; | |
} | |
path = path.replace(`{${key}}`, vals[key]); | |
} | |
return path; | |
} | |
return { propIds, formatPath }; | |
} | |
export function withDbData(specs) { | |
let propToSpecs = {}; | |
for (let spec of specs) { | |
let {propIds} = parseSpec(spec); | |
for (let propId of propIds) { | |
if (!propToSpecs[propId]) { | |
propToSpecs[propId] = []; | |
} | |
propToSpecs[propId].push(spec); | |
} | |
} | |
return (Child) => { | |
let Wrapper = class extends React.PureComponent { | |
constructor(props) { | |
super(props); | |
this.unmounting = false; | |
this.offs = {}; | |
this.state = {}; | |
} | |
subscribeToSpec(spec) { | |
let { name, keys } = spec; | |
let { propIds, formatPath } = parseSpec(spec); | |
let path = formatPath(this.props); | |
if (!path) { | |
return; | |
} | |
let ref = firebase.database().ref(path); | |
let offFunc = ref.on('value', (snap) => { | |
let dat = keys ? filterKeys(snap.val(), keys) : snap.val(); | |
if (equal(dat, this.state[name])) { | |
return; | |
} | |
this.setState({ | |
[name]: dat, | |
}); | |
}); | |
let hasBeenOffed = false; | |
let off = () => { | |
if (hasBeenOffed) { | |
return; | |
} | |
hasBeenOffed = true; | |
if (!this.unmounting) { | |
this.setState({ | |
[name]: null, | |
}); | |
} | |
ref.off('value', offFunc); | |
}; | |
for (let propId of propIds) { | |
if (!this.offs[propId]) { | |
this.offs[propId] = []; | |
} | |
this.offs[propId].push(off) | |
} | |
} | |
componentDidMount() { | |
for (let spec of specs) { | |
this.subscribeToSpec(spec) | |
} | |
} | |
componentDidUpdate(prevProps) { | |
let resubs = new Set(); | |
for (let prop of Object.keys(propToSpecs)) { | |
if (prevProps[prop] !== this.props[prop]) { | |
if (this.offs[prop]) { | |
for (let off of this.offs[prop]) { | |
off(); | |
} | |
} | |
this.offs[prop] = []; | |
for (let spec of propToSpecs[prop]) { | |
if (resubs.has(spec.name)) { | |
continue; | |
} | |
resubs.add(spec.name); | |
this.subscribeToSpec(spec); | |
} | |
} | |
} | |
} | |
componentWillUnmount() { | |
this.unmounting = true; | |
for (let offList of Object.values(this.offs)) { | |
for (let off of offList) { | |
off(); | |
} | |
} | |
this.offs = {}; | |
} | |
render() { | |
for (let spec of specs) { | |
if (spec.await && !this.state[spec.name]) { | |
return null; | |
} | |
} | |
let childProps = Object.assign({}, this.props, this.state); | |
return (<Child {... childProps} />); | |
} | |
} | |
return Wrapper; | |
} | |
} |
@zomars coming up soon 😃
@dsafreno I think I beat you to it 😅
https://gist.github.com/zomars/0fa63604baf12517c5b7b592c1c72397
how about a firestore version?
would that be quite similar?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How about a React Hook version? Just for fun