So far, it was fine to rely only on Reactâs local state and Reactâs Context API. This tutorial dives into using MobX on top of React and Firebase for the state management. Weâll exchange Reactâs local state (e.g. users on admin page, messages on home page) and Reactâs context (e.g. session management for authenticated user) with MobX. It will how you how to accomplish the same thing with MobX, so you can integrate it in your tech stack.
The first section will setup MobX, where weâll add a state layer separately from the view layer and connect MobX with React by providing the MobX stores Reactâs Context API to the React components. The second part exchanges the current React state layer with the MobX state layer:
If you are not familiar with MobX, I recommend The Road to Redux . Most of the MobX knowledge is required for the migration from only using React to MobX.
First, follow this short guide to enable decorators in create-react-app . You can also take the alternative way of not using decorators, to avoid the eject process, but this tutorial only reflects the usage with decorators . After you went through the MobX setup tutorial, you should have installed mobx and mobx-react .
For this run through, we focus on the MobX setup without worrying about Firebase or React. First, you need the MobX stores, so we create a folder with files. From your src/ folder type:
Thereâs a store for the session state (e.g. authenticated user), a store for the user state (e.g. list of users from the database), and a store for the message state (e.g. list of messages from the database). There is also an entry point file to the module to combine those stores as root store, the session store which manages the authenticated user. The authenticated user represents the session in the application.
Next is the user store that deals with the list of users from the Firebase realtime database. It sets either the object of users as a dictionary or a single user identified by a unique identifier. It also has a userList property to retrieve the user object as a transformed list of users:
Third, the message store that is similar to the user store. It manages one more property for the pagination feature:
Finally, combine all three stores in a root store. This can be used to make the stores communicate with each other, but also to provide a way to import only one store (root store) to have access to all of its combined stores later. In src/stores/index.js file:
The MobX setup is done. Now, you can connect your state layer with your view layer. The MobX stores can be provided to the component hierarchy using MobXâs Provider component. This time, the Provider component from the MobX library passes down the all stores instead of only the authenticated user. In src/index.js file:
Thatâs it for connecting both worlds. Next, weâll refactor almost everything from Reactâs local state to MobX. We want to have everything in the MobX stores that should be persisted when we navigate from route to route. This includes users, messages, and the authenticated user, but maybe not the loading states.
So far, we have managed the authenticated user with Reactâs Context API. We provided the authenticated user in a Provider component and consumed with a Consumer component. Letâs change this by storing the authenticated user in MobXâs session store instead, and injecting the store to all components who are interested in it.
In the authentication higher-order component, we make the session store in the props of the component:
Then, we use the store to persist the authenticated user like before, by setting it to Reactâs local state. We donât need to provide the authenticated user with Reactâs Context Provider component, because it will be available for every component that connects to the store:
Weâve stored and provided the authenticated user to the MobX session store. Letâs see how we can consume it in the Navigation component for the conditional rendering of the routes without Reactâs Context:
MobX injects the store in the Navigation component with the inject higher-order component to make a decision for the conditional rendering, but also to pass the authenticated user to the child components. In addition, the observer higher-order component makes sure that the component updates when something in the session store has changed. We can do the same in our other components that are interested in the authenticated user. For instance, the authorization higher-order component can rely on the MobX session store as well:
Also, our other authorization component for the email verification can make use of it:
The AccountPage component displays the authenticated user, but also renders the component that manages sign in methods for the user:
You can remove the React Context we used for providing and consuming the authenticated user in the src/components/Session/context.js and src/components/Session/index.js files:
Thatâs it for storing the authenticated user in our MobX session store which takes place in the authentication higher-order component and for consuming the authenticated user in every component which is interested in it by injecting the session store to it.
We have implemented the session management with the authenticated user with MobX instead of Reactâs local state and context API. Next, we will migrate the user management over to MobX. The users are mainly used in the AdminPage componentâs UserList and UserItem components. Our goal here is to navigate from UserList to UserItem with React Router without loosing the state of the users. The UserList component fetches and shows a list of users, the UserItem component fetches and shows a single user entity. If the data is already available in the MobX user store, we only need to keep track of new data with the realtime feature of the Firebase database. We begin with the UserList component:
React MobXâs inject higher-order component is used to marry React with MobX and the observer higher-order component makes sure to update the component if the store has changed. Next, make sure users are fetched from Firebaseâs real-time database and persisted in the MobX store:
Now every time the Firebase listener is called or because a user was added, edited, or removed from the list, the most recent user object with users from Firebase is stored to the MobX user store.
Another user experience improvement is adding a loading indicator when there are no users in the store. Every other time, when there are users in the store but the Firebase listener is updating the MobX store with a new user object, we donât show any loading indicator:
The users are not managed in the local state of the component anymore, but in MobX instead. The users and loading indicator are rendered like before, but only the loading state comes from the local state. The Link component only navigates to the UserItem component, but it doesnât send any user objects. We wanted the user at our disposal before via the Link component, we want to let MobX handle it this time.
Thatâs it for the UserList component. It renders a list of users as before, fetches the recent user object with all users from Firebase with a realtime connection, except it stores the result into the MobX user store instead of Reactâs local state. Letâs continue with the UserItem component that will also connect to the MobX user store:
Similar to the UserList component, the UserItem component receives the MobX user store. Check again your user store to see whatâs happening when setting users or a single user or when retrieving users with the computed methods. Next, letâs make sure the user is fetched from Firebaseâs realtime database and persisted in the MobX store:
When the component mounts, we register Firebaseâs listener to the real-time database. When the user changes, we update it in the MobX user store. If there is already a user, we donât show a loading indicator. If there is no user, we show it:
We can render user and loading state as before, except the user comes from the user store which comes from the props:
Thatâs it for the UserItem component. It renders a user, fetches the recent user from Firebase with a realtime connection, only it stores the result into the MobX store. The advantage in using MobX instead of Reactâs local state is that user states persist between routes. This means you wonât need to fetch them when you navigate from UserItem to UserList or a different route, since they remain in MobXâs global state.
We migrated the users and session management from Reactâs local state and Reactâs Context API over to MobX. Finally, weâll migrate the message entities from the Firebase database. It uses the authenticated user as well, which is why we have refactored the session management earlier. Whatâs missing is a connected Messages component to the MobX message store to store and get messages:
The Messages component has access to the session store, too, which is later used for associating the user to the written message. Because we are using the MobX stores, only a couple of properties are left in the local state of the component:
The Messages component only deals with the loading indicator and the text for the message written from within the component as local state. Everything else will be managed with MobX.
The other logic for creating, updating, and removing a message stays the same, because it only uses the Firebase API. The listener of the Firebase database makes sure to update all messages in the MobX message store. Only the class method to update the limit uses the method provided by the MobX message store:
Every time this state in the MobX message store changes, we receive the new limit in the Messages component as props due to the connected higher-order component. If the limit changes, we register a new Firebase listener with the new limit:
Rendering the component didnât change much, except now it receives the messages from the message store from the props instead of the local state.
The MessageList and MessageItem components didnât change at all, only the HomePage and the Messages inject the MobX stores and observe their changes. Every time a Firebase listener that receives the latest entities from the Firebase database is called, it stores the result in on of the MobX stores. It happens as well when a user creates, edits or deletes a message. If the limit for the pagination feature changes, the listener is registered with this new limit again. Everything else, including the text of the new message or the loading indicator, is still managed in Reactâs local state.
Youâve introduced MobX as state management library to manage your session, user, and message state. Instead of relying on Reactâs context API for the authenticated user object and Reactâs local state for the list of users and messages from the Firebase database, these objects are kept in the MobX stores. The project is found in GitHub repository .