Last active
August 3, 2019 21:22
-
-
Save olegpolyakov/b9afbe25a87565eba69703fa7040cfe0 to your computer and use it in GitHub Desktop.
React Firebase HOC
This file contains hidden or 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, { 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