Skip to content

Instantly share code, notes, and snippets.

@dsafreno dsafreno/withDbData.js
Last active Oct 21, 2019

Embed
What would you like to do?
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;
}
}
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.