Managing Firebase Realtime Data with MobX

Featuring React, MobX atoms, and *insert realtime datasource here*

Sampson Oliver
5 min readOct 23, 2017

Any old data store paradigm can work with firebase, or other socketed data providers for that matter, but clean and happy implementations are oft elusive. This article demonstrates how we can use MobX to create a datastore that not only handles realtime data and observer updates gracefully, but also one that is aware enough to hold its connections open only as required.

A quick note on definitions — I’ll refer to ‘websockets’ a lot throughout this article, as this is conceptually how I view an instance of a firebase database listener. You can apply the design pattern within this article to any socketed realtime data source, and should.

Prequel / A word about Flux

Probably one of my greatest epiphanies in the React-landscape was when I realised I didn’t have to use redux for state management all the time. Redux isn’t bad, nor is the flux pattern in general, but it’s not a perfect tool for all solutions. Dan Abramov himself warns against overuse, and this post makes many good points about when and when not to redux.

I won’t rehash those great articles — just read them. But one area of weakness I have found specifically is when coupling redux with firebase realtime database, and more generally with any realtime data being piped to the application via a websocket-like fashion.

Flux patterns follow an event driven ideology, which you’d think would be great for a pipe of data events hitting your application. Unfortunately, the issue with firebase — and other websocket datasources — is that the event dispatch-reduce-store flow is usually already being taken care of by the serverside infrastructure to some extent. With flux, you write a lot of code simply so that once you receive a websocket event, you can then dispatch and reduce that same event as-is just to put it in the store. Boilerplate galore.

MobX —100% Realtime Data, 0% Fuss

MobX is really very good at the ‘store’ part of the dispatch-reduce-store chain, but doesn’t really care how the data gets there. This helps with our boilerplate predicament, because we can just ingest event data from our websockets, without fuss, into the store.

On top of that, it takes two lines of code to move from having some data, to having an observable data store that auto-updates its listeners of relevant changes. I won’t talk much about how MobX does its magic as others have covered that topic better, but the gist is that you create a mobx store as an observable entity, and register components as observers that auto-update when the store’s data changes. It might look something (loosely) like this:

import {observable} from "mobx";
import {observer} from 'mobx-react';
class Foo {
@observable bar = "";
}
@observer
class BarObserver extends React.Component {
render() {
return (
<p>${this.props.Foo.bar}</p>
);
}
}

This is great, because as bar changes, so will the observer be updated. So you can imagine that if we have some websocket open that updates bar on external events, then we don’t need to make any further changes to the observer whatsoever to be able to react to those changes.

Becoming “observer-aware” to manage listener state

So pretty much out of the box, MobX is a great tool for connecting a realtime datasource such as firebase to an observable data store, and connecting observers to that store. The next big trick is managing the state of the websockets and their listeners — in Firebase, this means knowing when to connect to a reference and start listening to events.

You could be tempted to just open your websockets as needed within your app state, and move on. The problem there is that your application lifecycle, and usually by association, your component lifecycle starts to become very tightly coupled to your websocket lifecycle. Either your components open and close websockets when they come in and out of scope, or you open all websockets when you go to a particular ‘screen’ and are responsible for closing them when you leave it.

You could also be tempted to open all your websockets as part of app startup, have them stream data to a store through the entire app lifecycle, and then use mobx as we did above to observe that store. But of course this negatively affects your data usage, and there will naturally be some websockets that rely on as-yet unknown data or variables.

What we want is all our components and screens to just interact with our data store(s), and our data store(s) to handle opening and closing our websockets. We can do this in MobX via an atom.

Here, a controller (e.g. React) manages the lifecycle of an observer (e.g. a view component). When that component listens to data from the atom store, the atom store returns its current state and opens a websocket and starts listening for non-local data events. When the controller destroys the observer, the atom store stops listening on the websocket and tears it down or suspends it.

MobX Atoms

You’d be forgiven for not knowing about MobX’s atoms, as they’re pretty well hidden. My impression (without having dug deeply through the source) is that Atoms expose part of MobX’s underlying magic of tracking what observers are observing an observable and hence notifying them of updates. With an atom, you can track when something becomes observed and unobserved, and in our case, we can use that to know when to open or close our websocket!

We’ll actually totally eschew MobX’s observers from here on, and just be using atoms. An atom provides a start and stop callback, and we’ll use these to start and stop listening to events happening on the websocket. Simple really!

Making an observer-aware store with atoms

So let’s get to it. I’ll be showing code with some loose typings in Flux, for clarity.

import { Atom } from "mobx";class Store<T> {
atom: Atom;
data: T
ref: firebase.database.Reference;

constructor() {
this.atom = new Atom("Store",
() => this.ref.on("value", this.valueListener.bind(this)),
() => this.ref.off("value", this.valueListener.bind(this))
);
}
valueListener(snapshot: firebase.database.DataSnapshot) {
this.data = snapshot.val();
this.atom.reportChanged();
}
getData(): ?T {
this.atom.reportObserved();
return this.data;
}
}

So the important bits: the atom constructor takes two callbacks to attach and detach your websocket datasource to the store — for a firebase value listener, we call on and off respectively. Next, the reportObservedcall in the getData() method (which is what our observers will call to fetch data) tells the atom that it’s being observed. Last, the reportChanged call in the websocket listener informs the atom that it should update all of its observers with new data.

And that’s it really! Atoms don’t need to be told when they become unobserved, they just know, so observers and views can do things like this:

@observer 
class Observer extends React.Component {
render() {
return (
<p>{this.props.Store.getData()}</p>
);
}
}

You’ll notice that your websocket only gets opened for the first observer that subscribes to the store, and remains open until all observers have unsubscribed (with React components, this will happen naturally when the components cease to be rendered).

For anyone keen to checkout the source for my atom bindings, you can see the full repo here:

or you can install the npm module with npm install mobx-websocket-store. Note that the precise API has evolved slightly over time within my own source.

--

--

Sampson Oliver

Team lead @acloudguru, serverless junky, runner, feminist, & infrequent music producer. @sampsonjoliver on GitHub/twitter et al