Skip to content

Instantly share code, notes, and snippets.

@ilionic
Forked from steida/gist:570d007c94a25fa68443
Last active August 29, 2015 14:22
Show Gist options
  • Save ilionic/27e7bfe486c9a34a86e1 to your computer and use it in GitHub Desktop.
Save ilionic/27e7bfe486c9a34a86e1 to your computer and use it in GitHub Desktop.
Este Firebase higher order component
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
}
@ilionic
Copy link
Author

ilionic commented Jun 17, 2015

Usage

Edit = listenFirebase(Edit, {
  ref: (firebase, props) => firebase
    .child('groups')
    .child(getGroupIdFromUrl(props)),
  action: actions.onGroup
})
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