Managing State with Firebase and React.

2 weeks ago I released @react-firebase (/database, /auth and /firestore).
You can read more about it here.
@react-firebase makes Firebase integration in your app trivial,
so you can focus on the creative aspects specific to your app.
This post is part of a series on building apps with React and Firebase.
We will build the same app twice.
In the first attempt, we will not manage any local state.
In the second, we will map our Firebase Data to a local state and inject it to the components that need it.
Outline
- Component Description.
- Data Shape
- Setup.
- UI
- Read data from Firebase in your React app.
- Decouple your firebase queries from the UI with PORS (Plain Old React State)
Component Description
By the end of this post we should have a React component that loads our data, and renders it. ( Making it look nice is left as an exercise to the reader 😅)
For the rest of the post, we will assume that our data exists in our Database and that it has the shape detailed below.
Data Shape
Our data has 2 collections :
- users (indexed by user_id):
users/{user_id}/ - posts (indexed by post_id):
posts/{post_id}/
If you want pointers on structuring your data, check out the excellent Firebase guide.
Check out
generate-firebase-datato generate some fake data.
Setup
We’ll be using @react-firebase/database to interact with Firebase.
Make sure to follow the setup instructions here.
The same techniques used here can be applied with any of the following packages :
UI
Our UI exports component has 2 children, Post and Author. Both accept a data prop that they use when rendering.
Reading data from Firebase in your React app.
Let’s create a new Component that should be responsible for listening to and rendering our data.
Wrapping our data listeners in React components allows us to unsubscribe from listeners when the UI that needs it is off screen and re-subscribe when needed.
Note that the code above waits for both the post and the user to be loaded before rendering.
We probably want to avoid doing that in our app.
A slightly better way would allow us to render the post data as soon as it’s ready and then render the user when it becomes available.
This will look something like this :
This works and will be relatively fast.
However as our data fetching needs and UI code become more complex, interweaving our data loading components with our UI components, while being great for straight-forward requirements, will add unnecessary complexity and become more difficult to think about as our app grows.
In the next sections we’ll explore how to decouple the raw data fetching components from the ui ones.
To do that, we’ll need to introduce some state to our component.
The state should be updated by our Firebase Nodes and read by our UI nodes.
We will be lifting state up from a leaf data node to our App component and then passing it back down to the component that cares about it.
In this use-case we want our state to have 3 fields postIds, users and posts.
Let’s start by refactoring our App component by changing it’s render method to render the UI and the Headless data fetchers.
The HeadlessDataLoader component used above should render null and send data upwards using the onData prop.
The onData prop can send anything, in order to update our state we will need it to send among other things :
postIds: Array<string>posts: { [postId: string]: { author_id:string } }users: { [userId: string]: { username: string }}
In addition to that we need a way to know who is calling onData.
So let’s define our onData method type :
And now we can write our Headless Component that loads our required data and sends it up by calling onData with the right action.
Now in the parent component we can map these actions to a new state.
And our final component :
Recap
We can avoid managing state altogether with React and Firebase by using the render-less components provided by @react-firebase/database.
To make it easier to separate your data fetching components from your UI ones, we suggested the use of a Headless Component that sends data up using an onData prop.
The data emitted is then used to update the state higher in our hierarchy, which is flowed back down using the data prop.
This allowed us to have a clean wrapper component co-locating the data listeners and the UI.
That’s all folks !
