Skip to content

Instantly share code, notes, and snippets.

@steida
Last active October 10, 2015 18:05
Show Gist options
  • Save steida/570d007c94a25fa68443 to your computer and use it in GitHub Desktop.
Save steida/570d007c94a25fa68443 to your computer and use it in GitHub Desktop.
listenFirebase higher order component
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;
}
@steida
Copy link
Author

steida commented May 18, 2015

Usage

Edit = listenFirebase(Edit, {
  ref: (firebase, props) => firebase
    .child('groups')
    .child(getGroupIdFromUrl(props)),
  action: actions.onGroup
})

@steida
Copy link
Author

steida commented May 21, 2015

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