Build your first app with Mobx and React
MobX is another state management library available for React apps besides Redux and Context API. However, MobX is not just a library for React alone, it is also suitable for use with other JavaScript libraries and frameworks that power the frontend of web apps. The MobX >=5 version runs on any browser that supports ES6 proxy.
Major concepts
Here are the major concepts of mobx:
Observable
The observable allows us to turn any data structure or property into an observable state so that other things can keep track of these observable changes and values.
Action
The action allows us to change the state i.e. values of observable. The state data should not be modified outside actions to ensure code scalability.
Computed
The computed property allows us to derive some value based on the state change. The computed values are obtained by performing some sort of calculations on observables.
Observer
The observer allows us to keep track of changes in observables so that React gets notified on any change and starts re-rendering. They are provided by the mobx-react package.
Store
The store is the source of data. Unlike redux, in mobx data and functions which change the data live in the store. So a store may contain observables and actions.
Now let’s put these concepts into practice.
We are going to create a simple application where users can react to images and comment on it, similar to Facebook. Here’s the link to the demo.
Project setup
Assuming prior knowledge of React, you need to have NPM and Node.js installed on your machine.
I am using custom webpack configuration and setting up the project to enable decorators. Don’t worry, there’s an alternative way to do this without decorators as well. For this example, I’m using decorators anyway since it’s more concise. But I’ll mention the alternatives as well. If you’re using create-react-app you can skip these setup steps.
Pull the master
branch from this repository for initial setup.
Run yarn
to install dependencies and start the project using yarn start
. The app will run on http://localhost:8080.
Setup for decorators
The following plugins are required to enable ESNext decorators.
yarn add --dev @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators
Then add the following configuration to .babelrc
file.
Styles
Pull design branch for styles. All the styling is under the css
folder inside the src
directory. Here's a visual of our app components.
- Card component with:
- Randomly generated image.
- Count component to keep track of the number of likes and comments.
- Button component with Like and Comment buttons.
- Form component with the input field to post a comment.
- Comments component with a list of comments.
Installing Dependencies
Install mobx
state management solution and mobx-react
library to connect the state layer to the React view layer.
yarn add mobx mobx-react
Now we will actually start adding features using Mobx.
Store
First, we’re going to create a Store.jsx
under store
folder.
Here we’ve created a Store class with likesCount
as an observable state, updateCount
as an action to modify the state and then exported a new instance of the Store.
If your setup doesn’t support decorators the above code can be re-written as:
Then we make the store accessible throughout the app by passing it using the Context API in main.js
Now we can access the store and its class properties in Count.jsx
using useContext
. Since we've set the initial value of likesCount
to 12, your app will render that value.
Remember that the observable state can only be modified via actions. So in order to increment likesCount
when user clicks on Like button we are going to use updateCount
action from the store that we've already defined. Handle onClick
action in Buttons.jsx
.
If you click on the Like button you won’t see any changes.
To observe and react to changes in a functional component, we can either wrap the component in observer function or implement useObserver hook, like below. So let’s update Count.jsx
as:
Now the like count updates when you click on the button.
Comments
Let’s start working on the comments section.
An array data structure can be observable as well. Let’s create an observable comments
field. Add the following in Store.jsx
.
@observable comments = ["Wow", "awesome"]
Then access the comments property of Store class from Comments.jsx
like we did before in Count.jsx
using useContext
. The Comments component will now render the comments from the store.
We also need to allow the user to add comments from the form.
First, let’s create an action called postComment
in the store that simply pushes the new comment into the previous array of comments. Add the following lines of code in Store.jsx
.
@action postComment(comment){
this.comments.push(comment)
}
Then update the Form.jsx
component as:
Here we’ve simply created a function that calls the store’s postComment
action when the user submits the comment and set the input field to empty after submit.
To update the comments component when a new comment is added, we need to make the Comments component an observer as we did with Count. So in Comments.jsx
wrap the content to be returned with useObserver
. Also, don't forget to import useObserver
.
Now if you write any comment and hit enter your list of comments will automatically update.
Let’s focus on the input field when you click on the comment button. We can simply use HTML DOM focus( ) method. But first, let’s give the input field an id.
Then add focus method on onClick
handler of comment button in Buttons.jsx
component.
Now when you click on the comment button the comment field is focused.
Computed
Now in order to get the count of the comments, we are going to create a commentsCount
getter function that computes the observable comments
array's length. MobX will ensure commentsCount
updates automatically whenever comments
array changes. In Store.jsx
add the following:
@computed get commentsCount(){
return this.comments.length;
}
Then simply update the following lines in Count.jsx
.
<div className="col-sm" align="right">
{store.commentsCount} comments
</div>
You’ll also notice that when you add a comment, the count gets updated as well.
Services / API call
Making an API call and asynchronous codes are frequent in applications. Since this is a custom webpack configuration to enable async/await update the .babelrc
file with the following.
or else you might run into this error
Let’s change the image in theCard.jsx
component on button click. We are going to use this fun and free API to fetch the characters' images from the Rick and Morty show. Check out their documentation for more details.
You’ll find from this section that we can get a single character by adding the id
as a parameter: /character/1
https://rickandmortyapi.com/api/character/1
Let’s create an image store with observable imageUrl
containing default value. Then we create a fetchImage
action that returns the JSON response of a single character.
After await
a new asynchronous function is started, so after each await
, state modifying code should be wrapped as action. There are multiple ways to do this. Read this section of Mobx documentation for more details.
One way is to use the runInAction
, which is a simple utility that takes a code block and executes in an anonymous action. Here we are wrapping the state modifying part after await in runInAction
.
You can also run only the state modifying part of the callback in an action. Here we’ve created an action to set the URL outside the fetchImage
and then called it as required.
Then in Card.jsx
component
- Import the
imageStore
and set the source of the image to the observableimageUrl
from the store. - Implement
useObserver
to react to changes. - Add a button with
onClick
handler that calls thefetchImage
to get the image URL.
Aaand we’re done! Here’s what your final output will look like:
#Note
The nice thing about bundling actions with stores is that we can use them in onClick
handlers. Which means most of the components, like in this example, can be stateless functional components. To make a class component an observer we can use @observer
decorator or wrap the component with observer
function.
import React from "react";
import { observer } from "mobx-react";//With decorator
@observer
export default class Form extends React.Component{
...
}//Without decoratorclass Form extends React.Component{
...
}
export default observer(Form )
Mobx docs are well-written and contain a lot of best practices.
You can find all the code of this example here —
And that’s it. Thanks for reading! 😃