Connecting Firebase and Redux
The Firebase Realtime Database is great for building websites and Single-page applications(SPAs) without having to write any server code, but it has a few limitations:
- The Javascript SDK takes a really long time to initialize. The process of loading the SDK(~90kb), initializing
firebase
, retrieving the logged in user and loading some data can take easily 10 to 20 seconds on a cold start, even on a good network connection. - The realtime database does not support offline caching or persistence on the web (it uses
localStorage
internally but doesn’t let you use it), so it’s really hard to make your app work offline, or improve the cold start performance by loading locally persisted data. - Using Firebase makes code hard to test and debug. Close-coupling of your UI code and application state with Firebase makes it difficult to reason about individual components in isolation.
To work around these limitations, it’s advisable to maintain the state of your applications separately, and sync it with Firebase only when required. TL;DR:
Don’t mix UI code and Firebase code.
Redux is great for state management in front-end applications, and it addresses the concerns listed above fairly well:
- The entire state of your application lives in a single plain Javascript object in the client (browser), and it can be initialized easily with default or persisted data.
- Persisting the application state is straightforward, and so is loading it back.
- Redux is great for testing as you’re dealing mostly with pure functions and plain javascript object, so it’s very easy to test them in isolation.
If you’re new to Redux, check out this video tutorial or read the docs. The rest of this post assumes familiarity with the basic Redux concepts like reducer, store, state, action, dispatch etc.
Firebase → Redux
Here’s how we’ll connect Firebase and Redux:
- Listen for changes in portions of the realtime database and dispatch actions to update the state in the Redux store whenever the data changes.
- Listen for changes in portions the state managed by the Redux store, and make changes in the realtime database based using the updated state.
As an example, let’s listen for changes on the element at the path /message
in the realtime database and let’s keep it in sync with state.message
in the Redux store.
Here is the code for the Redux store:
Now, let’s define a function fromDb
that accepts as arguments a Firebase database
instance and the store’s dispatch
function and dispatches an action to update the store whenever data changes in the database
.
Now, we can call fromDb(firebase.database(), store.dispatch)
anywhere in our code, and the Redux store will be updated whenever the data at /message
in the realtime database is changed.
You can play with a working example of fromDb
here (open the same link in two different tabs and try changing the data).
Redux → Firebase
In many cases, we would also like to modify the database whenever a change is made inside the store. For this, we can define a function fromStore
which takes the updated state
from the store and a firebase database
instance and updates the database.
To ensure that fromStore
is called whenever the state changes, we can add a change listener to the store by calling store.subscribe(() => fromStore(store.getState(), firebase.database()))
. Now, whenever the store state changes, the database is updated.
You can play with a working example of fromDb
here (it’s the same as before, but now changes are written to the store instead of the database).
Firebase ⟷ Redux
More often than not, we would want to set up listeners on both sides together, so that the store is updated whenever the data in the database changes and vice versa. To do this, let’s define a helper function called linkStoreWithDb
:
linkStoreWithDb
takes two functions: fromDb
and fromStore
as arguments, and returns another function as its result. The resulting function can be called with the database
instance and the Redux store
as arguments, and it invokes fromDb
and fromStore
to set up the desired two-way binding. Here’s how we can use it in our code:
Firebase ⇎ Redux
Our setup for connecting the realtime database and the Redux store is looking good so far, but is has a couple of important shortcomings:
- There is no way to turn off the two-way bindings. This could be a problem in complex Single-page applications where we might end up with dozens of dead listeners, or worse still, unexpected data changes.
- Data changes in the realtime database trigger
fromDb
, which dispatches an action to update the store. This in turn invokesfromStore
which tries to update the Firebase database with the same data. It would be best to avoid this wasteful write.
Let’s make some changes to fromDb
and fromStore
to address these issues:
The variable mustUpdate
is used to track whether or not fromStore
should write to the database. It is set to false
immediately before dispatching the action inside fromDb
and back to true
immediately afterwards. This prevents the wasteful write.
fromDb
now returns a function which can be called to remove the listener attached to the database. We’ll use it inside linkStoreWithDb
, which is defined below:
Apart from setting up the listeners, linkStoreWithDb
now also returns a a function unlink
which can be called to remove the listeners and turn off the two-way binding.
If you’re using React, you can call linkStoreWithDb
and unlink
inside the component lifecycle methods to set up bindings for the data required by your component, and remove them when they are no longer needed:
Firebase ❤️ Redux
Our code for connecting the realtime database and the Redux store is quite robust now. However, this seems like a lot of work to set up a simple two way binding. While linkStoreWithDb
is abstract and reusable, fromStore
and fromDb
contain some bits of reusable logic and some details specific to our example of binding '/message'
in the database to state.message
in the store.
Let’s extract the common logic into a new function:
Notice how we’ve replaced all the code specific to the message
example with calls to path
, actionCreator
or selector
. linkStoreWithPath
additionally also keeps track of the previous state, and writes to the realtime database only if the incoming state is different. This prevents redundant writes.
We can now use it for to reimplement linkMessage
as follows:
Isn’t that much better? Not only is it a lot less code, it’s a lot simpler, and there’s practically no logic involved except specifying the following:
path
: The location of the data in the realtime databaseactionCreator
: A function that takes the value returned from the firebase snapshot and returns an action that can be dispatched to set the corresponding value in the store.selector
: A function takes the application state, and extracts the part that needs to be written.
So you want a library?
Now that we’ve understood how to connect Firebase and Redux, the natural next step is to ensure that we never have to actually implement it. firebase-redux is a nifty little library that provides the functions linkStoreWithDb
and linkStoreWithPath
so that you don’t have to write them. Check out the README for more details.
Thanks for reading! I hope you find this article and the library useful. In the next article (if there is one), we’ll go one step further and throw React into the mix and see how we can make React, Redux and Firebase all work together seamlessly. Have fun!