Skip to content

Instantly share code, notes, and snippets.

@dsafreno
Last active July 21, 2020 17:14
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dsafreno/43850a4333b4a56a57f4e64d730ccc8b to your computer and use it in GitHub Desktop.
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
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
Copy link

zomars commented Dec 16, 2019

How about a React Hook version? Just for fun

@dsafreno
Copy link
Author

@zomars coming up soon 😃

@zomars
Copy link

zomars commented Jan 14, 2020

Copy link

ghost commented May 25, 2020

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