Last active
October 10, 2015 18:05
-
-
Save steida/570d007c94a25fa68443 to your computer and use it in GitHub Desktop.
listenFirebase higher order component
This file contains 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 Component from './component.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 | |
// But unfortunately querystring is not part of serialization. Kato from | |
// Firebase denied to update toString implementation. | |
.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(BaseComponent, args) { | |
// TODO: Add invariants. | |
const {ref, action, lazy, granular} = args | |
class ListenFirebase extends Component { | |
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 <BaseComponent {...this.props} {...this.state} />; | |
} | |
} | |
ListenFirebase.displayName = `${BaseComponent.displayName || BaseComponent.name}`; | |
return ListenFirebase; | |
} |
Room = listenFirebase(Room, {
lazy: (props) => !props.active,
ref: (firebase, props) => firebase
.child('rooms-messages')
.child(props.room.id)
.orderByChild('createdAt')
.limitToLast(MESSAGES_PAGE_LENGTH),
granular: true,
action: (event, snapshot, prevChildName, props) => {
onRoomMessages(event, snapshot, prevChildName, props.room.id)
}
})
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage