React and Firebase, sittin’ in a tree
This is a post about the wonders of using React and Firebase together. In the first half I talk architecture and the second half we get into some code.
Enjoy!
It’s coming up to the four week mark since I started building Malla. Yesterday I drew a line in the sand and called it an MVP. I am pleased with what I have accomplished (as much as my self-loathing will allow) and quite convinced that the MVP would be a lot more M if it weren’t for two things:
- React. It’s the simplified mental model that makes all the difference. Whenever I feel like I’m starting to take React for granted I go and read this page again.
- Firebase. My love affair with Firebase is nascent but vigorous. It’s just so fast to work with and, as we will see, once you realise how it fits with React, that simple mental model spreads from the front end to the back end and all the bits in between.
But alas, it’s not all puppies and rainbows.
Firebase is weird.
But so is React. And they’re both the same sort of weird. And the weirdness soon turns into a pleasing string of ‘aha’s and now, for me, the whole thing feels like a finished jigsaw puzzle.
Pro tip: I wrote all of my Firebase code for an imaginary app with an imaginary data structure. I feel that it helped to break free of the shackles of everything I already know about my own app. So the code snippets in this post that talk about ‘schools’ and ‘courses’ and ‘students’ were actually written before I went and incorporated them into my app, and they were all written in a snippet in Chrome’s dev tools.
Application architecture
Surely the two sexiest words in the English language.
I’ll get into the Firebase stuff soon, but first, here’s how an app without Firebase might look:
Now lets bind a human to that…
We’ll start with some ephemeral data: modal visibility. You could just store that in the a component’s state, if it was 2015, but it ain’t, so we’ll keep track of the currently showing modal in our store.
It goes a little something like this:
- A user clicks on ‘show help’. The component dispatches an action with the type SHOW_MODAL and a value of help (the action is created by actionCreators.js).
- The dispatched action updates the store.
- Elsewhere, a HelpModal component has been waiting patiently. The help modal’s spidey sense tingles when the store is updated and it runs its render method and sees that the value of visibleModal is now help. It gets all excited that it doesn’t have to return null any more so it renders some DOM and boom, user sees help modal.
- Our user reads the help (because users do that), then clicks close. That click event dispatches SHOW_MODAL again, this time passing null. That sets visibleModal to null, and poor HelpModal once more has nothing to offer us and recedes into the shadows.
That’s the basics out of the way, I hope you were reading quickly.
But what if we want to store some data? Oh I have an idea, let’s use Firebase!
So, what’s a Firebase?
Great question. Amazing question.
It’s a big ol’ database in the sky. You send requests to add/update/remove via the Firebase SDK that runs in the browser. You also listen for changes in the data and do stuff when said data changes.
You’ll notice (I hope) that I didn’t mention reading from Firebase. Cos you don’t. You either write, or set up a listener. And quite frankly I can’t think how all this would work if I had to do something so arcane as to read from the database every time I wanted to know something. How am I supposed to know when I want to know something?
Let’s look at how our app looks with Firebase woven in.
Kind of like a spaceship. Or a race car.
Now let’s look at an example of an interaction that involves data that we want to store.
The scenario: Bobby Brown is new in town and starting school today. A teacher wants to add him as a student to the fictional school management app (they don’t know it’s fictional). I’ll do it quick to start with, then slow later.
- The user clicks on a button called ‘add student’ and the component calls a function called addStudent.
- The addStudent function sends a message to Firebase telling it to add a student to the list of students.
- Firebase updates the data and emits a change event.
- A listener for ‘student added’ responds to the Firebase change and sends the details of the new student to the store.
- The store updates and emits a change.
- A component re-renders and the new student is rendered to the screen.
If you don’t know Firebase, and you’re a smart one, you may be thinking that this is some special type of idiocy.
Does he really wait until the database adds the student before rendering it to the page? Is he crazy? Did his mother eat a lot of lipstick while pregnant with him?
All valid questions, and this is where Firebase being weird comes to the rescue.
At step two, I send a message to Firebase to add a student. And I told you Firebase is up in the sky so you probably think this takes a few hundred milliseconds. But technically I’m sending it to the Firebase SDK, and the Fireabse SDK has more than just a pub/sub type arrangement with the big ol’ database in the sky. It is in fact a local copy of (some of) that database.
So when I tell Firebase to ‘add a student’, it immediately emits an event saying that there is a new student, my listener fires, the store updates and the new student renders.
When I say “immediately”, I mean next tick. Like, the store will have been updated before the line of code after the call to update Firebase is executed (I had to write console logs to test that this was actually true).
Why don’t I explain a little bit about each box. And because you’re a lazy so-and-so I’m going to put the image here again to save you scrolling. It’s almost identical to the one above.
- firebaseWatcher.js See, it’s all ass-about, I have to start at the bottom right! When the app first loads, I set up a bunch of listeners. If anything is added or changed in the database, that individual object will trigger a change event, and the appropriate action will be dispatched to the store.
- Store All of those listeners in firebaseWatcher.js fire when there’s a change, but also when they’re first bound, so this populates our store when the page loads (or when a user signs in).
- Component The fare usual here. Lots of stateless components just turnin’ store into pixels and waiting for the user to do something. Finally a user decides to add a new student to the system and the component jumps to the ready and calls an action…
- actions.js An “action”, in this case, is just a function that does something with the data it’s passed. Usually tracking an event for analytics and passing the data on to…
- firebaseActions.js This handles all the communications to Firebase. And apparently a lot like me, doesn’t pay attention to any response.
The data in Firebase is normalized (as is the store), so this file plays the role of ORM which means it’s mildly complex, but it has only one job: send data to Firebase.
Another kind of magical thing about Firebase: to remove a student from the database, and any references to that student in courses and schools all in one operation is easy. Easy I tell you. - And we’re back at the start. Firebase will emit an event, firebaseWatcher.js will send an action to the store which in turn emits a change event, and the components react and re-render themselves.
So you see React and Firebase are weird and great for the same reason. You have to learn to trust the system. With React, it was learning to trust that if your components are connected to the store correctly, you only need to worry about throwing data to the store. The UI will look after itself, and do so performantly.
It’s the same with Firebase. As long as Firebase is bound to your store properly via listeners, you can just throw data at it and know that the store, and thus your components and UI, will look after themselves, and do so performantly.
And guess what, if you want multiple clients updating in real-time as centralized data changes, you’ve already done it. You can see this in Malla, open it up in two browsers and as you arrange boxes and type in one window, it updates in the other. And there was literally no extra code required to do that. Just as having two react components both updating when the store changes isn’t very special.
SHOW ME SOME CODE!
If you know React and you know Firebase you’ve probably been asleep up until now, so let’s get dirty and look at some code.
If you’re not familiar with ES6 you need to go and learn ES6.
(The data structure referred to in all this code is at the bottom of the page.)
Remember back to our example of a user adding a student record? In the database, that student is added to the global list of students (a student ‘table’, if you must) and the ID for the student is added to the user’s list of students. So to know what students to show for this user, we first look at the list of student IDs for the user, then for each of those IDs we go to the global list of students to find the whole student record.
Those two things (list of student IDs and the actual student objects) are represented by two classes that both set up listeners. One of them listens to lists, and knows when something is added or removed. But it doesn’t send data anywhere, oh no! All it does is create another listener for the actual object that was added, or removes a listener if an item is removed.
That’s excellent, I hear you say, but I WANT MORE CODE.
OK so this is how you write data to Firebase. Since we have stuff normalized, when we do something like add a new student, we need to update more than one place in the database.
If you know ImmutableJs, it’s part setIn() and part withMutations(). That is, you can write to many branches of the data structure in one atomic update() call. You simply send an object with prop/value pairs where the prop is the path to the place you want to set the value. The value itself can be a deep object.
Combine that logic with ES6 syntactical goodies like computed property names and template literals and you have the funky but glorious syntax you see from line 63 below.
More Firebase weirdness: I have the unique keys of the objects I’m creating before I send them to the database by using an empty push() which returns the key that the new thing will have.
Bonkers.
Now let’s delete some stuff. You can call a remove() method on an item in the database, but if you have other things referring to that object, you need to clean those up too, and if you don’t like things going terribly wrong in the distant future you will want to do this in a single operation. So we’ll use our good friend update() and pass in some nulls, which gives the same end result.
Scenario: Bobby Brown has kicked the bucket and we want to remove him from the database.
- We remove his record in the list of students.
- And the reference to them that the user has.
- And the reference that the school object has.
- And we must look up each of the courses Bobby was enrolled in and remove the reference to him from there.
Starting with his ID, we first get his full record from the database. About an hour ago I said that you don’t ‘read’ from Firebase, you ‘listen’. Well sometimes you just want to read, which is done by listening ‘once’, then doing something with the result in a callback.
So you see, we generate the removeData object bit-by-bit, but we send it to db.update() in one go, so the removal is still atomic.
All up, not a lot of code for what is quite a complex operation.
And that’s what I like a lot about Firebase and React. I had some awful code as I was finding my way, but as I came to understand them better, and how they could work together, I refactored again and again and at some point it all started to fall into place as quite a simple set of steps.
If you want to check out how this sits in a real app with authentication and other complexities, I’m leaving the source for Malla out in the open for the time being so go have a peruse.
As promised, here’s the structure of the data we’ve been dealing with.