Skip to content

Instantly share code, notes, and snippets.

@olegpolyakov
Last active August 3, 2019 21:22
Show Gist options
  • Save olegpolyakov/b9afbe25a87565eba69703fa7040cfe0 to your computer and use it in GitHub Desktop.
Save olegpolyakov/b9afbe25a87565eba69703fa7040cfe0 to your computer and use it in GitHub Desktop.
React Firebase HOC
import React, { Component } from 'react';
const firebase = window.firebase;
const config = {
apiKey: "",
authDomain: "",
databaseURL: "",
storageBucket: "",
messagingSenderId: ""
};
firebase.initializeApp(config);
export function withFirebase({ refName, isArray }, getActions) {
return function (WrappedComponent) {
class Firebase extends Component {
state = {
[refName]: [],
};
actions = {};
componentDidMount = () => {
this.validateRefName(refName);
this.ref = firebase.database().ref(refName);
this.bind(isArray);
this.actions = getActions(this.ref);
}
componentWillUnmount = () => this.ref.off();
throwError = message => {
throw new Error(`ReactFire: ${message}`);
};
validateRefName = refName => {
let errorMessage;
if (typeof refName !== 'string') {
errorMessage = `Bind variable must be a string. Got: ${refName}`;
} else if (refName.length === 0) {
errorMessage = 'Bind variable must be a non-empty string. Got: ""';
} else if (refName.length > 768) {
// Firebase can only store child paths up to 768 characters
errorMessage = `Bind variable is too long to be stored in Firebase. Got: ${refName}`;
} else if (/[\[\].#$\/\u0000-\u001F\u007F]/.test(refName)) {
// Firebase does not allow node keys to contain the following characters
errorMessage = `Bind variable cannot contain any of the following characters: . # $ ] [ /. Got: ${refName}`;
}
if (typeof errorMessage !== 'undefined') {
this.throwError(errorMessage);
}
};
createRecord = (key, value) => {
let record = {};
if (typeof value === 'object' && value !== null) {
record = value;
} else {
record['.value'] = value;
}
record.key = record.id = key;
return record;
};
handleObjectValue = snapshot => {
const key = snapshot.key;
const value = snapshot.val();
const newItem = this.createRecord(key, value)
this.setState({ [refName]: newItem });
};
handleArrayValue = snapshot => {
const key = snapshot.key;
const value = snapshot.val();
const items = [];
snapshot.forEach(item => items.push(this.createRecord(key, value)));
this.setState({ [refName]: items });
};
handleChildAdded = (snapshot, previousChildKey) => {
const key = snapshot.key;
const value = snapshot.val();
const newItem = this.createRecord(key, value);
const items = this.state[refName];
let insertionIndex;
if (previousChildKey === null) {
insertionIndex = 0;
} else {
const previousChildIndex = items.findIndex(item => item.key === previousChildKey);
insertionIndex = previousChildIndex + 1;
}
this.setState({
[refName]: [
...items.slice(0, insertionIndex),
newItem,
...items.slice(insertionIndex)
]
});
};
handleChildRemoved = snapshot => {
const key = snapshot.key;
const items = this.state[refName];
const index = items.findIndex(item => item.key === key);
this.setState({
[refName]: [
...items.slice(0, index),
...items.slice(index + 1)
]
});
};
handleChildChanged = snapshot => {
const key = snapshot.key;
const value = snapshot.val()
const items = this.state[refName].map(item => {
if (item.key !== key) return item;
return this.createRecord(key, value);
});
this.setState({ [refName]: items });
};
handleChildMoved = (snapshot, previousChildKey) => {
const key = snapshot.key;
const item = this.state[refName].find(item => item.key === key);
const items = this.state[refName].filter(item => item.key !== key);
let insertionIndex;
if (previousChildKey === null) {
insertionIndex = 0;
} else {
const previousChildIndex = items.findIndex(item => item.key === previousChildKey);
insertionIndex = previousChildIndex + 1;
}
this.setState({
[refName]: [
...items.slice(0, insertionIndex),
item,
...items.slice(insertionIndex)
]
});
};
bind = isArray => {
if (Object.prototype.toString.call(this.ref) !== '[object Object]') {
this.throwError('Invalid Firebase reference');
}
if (isArray) {
this.setState({ [refName]: [] });
this.ref.on('child_added', this.handleChildAdded);
this.ref.on('child_removed', this.handleChildRemoved);
this.ref.on('child_changed', this.handleChildChanged);
this.ref.on('child_moved', this.handleChildMoved);
} else {
this.ref.on('value', this.handleObjectValue);
}
};
unbind = () => {
this.ref.off('child_added', this.handleChildAdded);
this.ref.off('child_removed', this.handleChildRemoved);
this.ref.off('child_changed', this.handleChildChanged);
this.ref.off('child_moved', this.handleChildMoved);
this.ref.off('value', this.handleObjectValue);
delete this.ref;
this.setState({});
};
render() {
const { add, update, remove } = this.actions;
return (
<WrappedComponent
{...this.state}
{...this.props}
add={this.actions.add}
update={this.actions.update}
remove={this.actions.remove} />
);
}
}
Firebase.displayName = `Firebase(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
return Firebase;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment