-
-
Save ilionic/27e7bfe486c9a34a86e1 to your computer and use it in GitHub Desktop.
Este Firebase higher order component
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 PureComponent from './purecomponent.react' | |
import React from 'react' | |
import {firebase, onFirebaseError} from '../firebase' | |
import {firebaseCursor} from '../state' | |
// This is super handy for monitoring listened refs. | |
function addListenedRef(ref) { | |
firebaseCursor(firebase => firebase | |
.update('listenedRefs', listenedRefs => listenedRefs | |
.push(ref.toString()) | |
.sort() | |
) | |
) | |
} | |
function removeListenedRef(ref) { | |
firebaseCursor(firebase => firebase | |
.update('listenedRefs', listenedRefs => { | |
const index = listenedRefs.indexOf(ref.toString()) | |
if (index === -1) throw Error('wrong removeListenedRef index') | |
return listenedRefs.delete(index).sort() | |
}) | |
) | |
} | |
// Firebase on/off helper leveraging React component lifecycle to prevent leaks. | |
export default function listenFirebase(Component, args) { | |
// TODO: Add invariants. | |
const {ref, action, lazy, granular} = args | |
class ListenFirebase extends PureComponent { | |
componentDidMount() { | |
this.maybeListenFirebaseRef() | |
} | |
// Remember, didUpdate is called only when props are changed. | |
componentDidUpdate() { | |
this.maybeListenFirebaseRef() | |
} | |
componentWillUnmount() { | |
if (!this.ref) return | |
this.unlisten(this.ref) | |
} | |
maybeListenFirebaseRef() { | |
const newRef = ref(firebase, this.props) | |
if (this.ref) { | |
// Ref can be changed, especially when url has been changed. | |
const refChanged = this.ref.toString() !== newRef.toString() | |
if (!refChanged) return | |
// Stop listening old ref. | |
this.unlisten(this.ref) | |
} | |
if (lazy && lazy(this.props)) return | |
this.ref = newRef | |
this.listen(this.ref) | |
} | |
listen(ref) { | |
addListenedRef(ref) | |
if (granular) { | |
this.ref.on('child_added', this.onGranularChildAdded, onFirebaseError, this) | |
this.ref.on('child_changed', this.onGranularChildChanged, onFirebaseError, this) | |
this.ref.on('child_moved', this.onGranularChildMoved, onFirebaseError, this) | |
this.ref.on('child_removed', this.onGranularChildRemoved, onFirebaseError, this) | |
this.ref.on('value', this.onGranularValue, onFirebaseError, this) | |
return | |
} | |
this.ref.on('value', this.onFirebaseValue, onFirebaseError, this) | |
} | |
unlisten(ref) { | |
removeListenedRef(ref) | |
if (granular) { | |
ref.off('child_added', this.onGranularChildAdded, this) | |
ref.off('child_changed', this.onGranularChildChanged, this) | |
ref.off('child_moved', this.onGranularChildMoved, this) | |
ref.off('child_removed', this.onGranularChildRemoved, this) | |
ref.off('value', this.onGranularValue, this) | |
return | |
} | |
ref.off('value', this.onFirebaseValue, this) | |
} | |
onGranularChildAdded(childSnapshot, prevChildName) { | |
this.callAction(['child_added', childSnapshot, prevChildName]) | |
} | |
onGranularChildChanged(childSnapshot, prevChildName) { | |
this.callAction(['child_changed', childSnapshot, prevChildName]) | |
} | |
onGranularChildMoved(childSnapshot, prevChildName) { | |
this.callAction(['child_moved', childSnapshot, prevChildName]) | |
} | |
onGranularChildRemoved(oldChildSnapshot) { | |
this.callAction(['child_removed', oldChildSnapshot, null]) | |
} | |
onGranularValue(snapshot) { | |
this.callAction(['value', snapshot, null]) | |
} | |
onFirebaseValue(snapshot) { | |
this.callAction([snapshot]) | |
} | |
callAction(args) { | |
// Async because Firebase dispatches local changes immediately, and it's | |
// not allowed for dispatcher to dispatch during dispatching. Example: | |
// userActions.save will make a change on some collection, which is | |
// already listened by someone else. Therefore timeout is a must. | |
setTimeout(() => { | |
action.apply(null, args.concat([this.props])) | |
}, 0) | |
} | |
render() { | |
return <Component {...this.props} {...this.state} /> | |
} | |
} | |
ListenFirebase.displayName = `${Component.name}ListenFirebase` | |
return ListenFirebase | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage