State management is the core of any React application and as React is just a UI library, we need something to take care of the state of our app. State management can become troublesome and it is easy to create unmanageable React applications because of inconsistent state.
In this article, we will learn about how to use MobX as our state management solution in a React Native application.
A State is just the data that your app is dealing with. State saves the data that a component requires and it influences how the component gets rendered. State management is the process of managing that data. Monitoring and retrieving data in a particular app can be difficult and that’s where state management libraries come to the rescue. There are multiple ways to manage states like using Redux or the React Context API, but here we’ll cover MobX.
MobX is a state management library that can be used with any JavaScript framework. React and MobX are powerful together and work as a complete framework. MobX provides the mechanism to store and update the application state that React Native then uses to render the components. The Philosophy behind MobX is: *“Anything that can be derived from the application state, should be derived. Automatically.”*
Derivations form the backbone of MobX which enables us to discard the repeated state. The idea is to:
Find minimal state (observable state), derive everything (derived state) and never turn the state into more state.
MobX at its core has three important concepts: Observables, Actions, and Reactions. A Store contains these three which then is used by the React Native application.
Observables with MobX are just containers that hold the core state of our application. The idea is to make an object able to emit new changes to which the observer can react. You can achieve this with the @observable
decorator.
Let’s imagine we have a variable named count
that changes over time. We can make it observable simply by:
// import observable from mobx
import { observable } from "mobx";
//create a store with count observable
class Store {
@observable
count = 0;
}
//export Store
export default new Store();
Remember the principle of MobX, *“Find minimal state (observable state), derive everything (derived state)”*.
The values that can be derived from already defined observables are computed values. MobX avoids inconsistency of state by discouraging the creation of more states. Imagine our count
variable holds the number of minutes by which something is delayed. We can add a computed
delay message that gets derived from the observable count
.
import { observable, computed } from "mobx";
class Store {
@observable
count = 0;
@computed
get delayMessage = () => {
return 'The train is delayed by' + this.count;
};
}
export default new Store();
Here, @computed
is working as a getter function deriving its value from count
. delayMessage
will automatically emit changes as the value of count
changes.
Actions are simply functions that modify the state. MobX supports uni-directional data flow, it means that once the action changes state, it automatically updates all the views which are consuming that state. Let’s add an action
that updates over the count
variable as the delay increases.
Store {
import { observable, computed, action } from "mobx";
class Store {
@observable
count = 0;
@computed
get delayMessage = () => {
return 'The train is delayed by' + this.count;
};
@action
updateDelay = delay => {
this.count = delay;
};
}
export default new Store();
Note that all state modifications must be done by actions only.
An observer subscribes to any change in observables and re-renders the components which use them. Reactions are just side effects of these state changes. It’s very similar to computed values but the difference is instead of computing and returning a value, a reaction simply performs a side operation. In simple words, Reactions are:
Side effects that should occur in reaction to state changes (component re-render)
MobX provides three main types of reaction functions: autorun, when and reaction.
autorun
is simply a function that runs every time the state changes.
autorun(() => {
console.log('delay is', this.count);
} );
The function runs each time the value of count
changes. The key is that we are not explicitly stating that it has to watch for changes in the count
variable. The fact that we have used count
inside autorun makes it as one of its dependency and that’s enough to trigger the function whenever the dependency changes.
when
triggers a side-effect whenever a certain condition is met. It takes two parameters. The first parameter is a function that gets reevaluated until it returns true and the second parameter is another function that runs once the first function returns true. A simple example could be:
class MyResource {
constructor() {
when(
// once...
() => this.count > 60,
// ... then
() => console.log("Guest is too late, maybe he's not coming");
);
}
}
Here, when
simply checks if the delay is more than an hour (60 minutes) then prints that he might not be coming.
reaction
is a variation of autorun
which gives more control over the data (dependency) used in the function. It accepts two function arguments and a third argument of options:
The following example shows a reaction that is invoked only once.
const reactionDemo = reaction(
() => this.count,
(count, reaction) => {
console.log("reaction demo: invoked. delay is " + count);
reaction.dispose();
}
);
this.count = 1;
// prints:
// reaction demo: invoked. delay is = 1
this.count = 2;
// prints:
// (There are no logging, because of reaction disposed. But, count continue reaction)
console.log(this.count);
// prints:
// 2
We’ll understand the working of MobX by creating a React Native app in three simple steps:
Here we’re building a simple app that gets images from Unsplash
and shows them to the user. Users can also click on an image and add it to their favorites.
The application uses the Unsplash API to fetch random images. You can generate an API key here.
If you have not created a project yet then follow the steps below:
$ react-native init UnsplashDemo
or, using Expo:
$ expo init UnsplashDemo
$ npm install mobx mobx-react
$ react-native run-<your-os>
Or, using Expo:
$ expo start
We’re going to search for some images and save the result. Then, we’ll allow clicking on any image to add it to our favorites. Comments are self-explanatory:
// importing observables and decorate
import { decorate, observable, action } from "mobx";
class Store {
// observable to save search query
text = '';
// action to update text
updateText = (text) => {
this.text = text;
}
// observable to save image response from api
data = null;
// action to call API and search images
searchImages = () => {
fetch(`https://api.unsplash.com/search/photos?client_id=${API_KEY}&page=1&query=${this.text}&orientation=landscape`)
.then(response => response.json())
.then(data => this.setData(data));
};
// observables can be modifies by an action only
setData = (data) => {
this.data = data;
};
}
// another way to decorate variables with observable
decorate(Store, {
text: observable,
updateText: action,
data: observable,
searchImage: action,
setData: action,
});
// export class
export default new Store();
Create a component ImageList.js
that will render the list of images. It will also show the images added to our favorites with a simple switch toggle.
import React from "react";
import { View, TextInput, Button, FlatList } from 'react-native';
// imports inject and observer from 'mobx-react':
import { inject, observer } from "mobx-react";
// components receive Store values as props which we will inject while exporting
function ImageList(props) {
// destructure variables from store to use
const { text, updateText, data, searchImages } = props.store;
return (
<>
<TextInput // TextInput to get search query from user
style={styles.input}
value={text}
onChangeText={updateText}
/>
<Button // Button to call API
title="Search"
style={styles.button}
onPress={searchImages}
/>
/>
<FlatList
data={data.results} // response from API
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<ImageView // reusable component to render image
source={{ uri: item.urls.small }} // passing the url
onPress={() => {}} // action to add item to favorite
/>
)}
/>
</>
);
}
// inject Store as props to ImageList and make it observe changes in Store
export default inject("store")(observer(ImageList));
We are just taking input from TextInput
and calling the Unsplash search API by pressing the Button
. The response is getting saved in the data
observable and we are using that in the FlatList
component to render a list of images. Simple, right? Now let’s move on to adding images to our favorites.
Go to unsplash.com/developers to learn more about the Unsplash API response.
As we know, actions are responsible for modifying state. So far, the updateText
mutated the text
observable and setData
mutated the data
observable. Now we want to add images to our favorites
, it means that we need one observable to store the state and one action to mutate this state. Let’s add them.
import { decorate, observable, action } from "mobx";
class Store {
text = '';
updateText = (text) => {...};
data = null;
searchImages = () => {...};
setData = (data) => {...};
// array to save favourite images
favorites = [];
// action to add images to favorites
addToFavorite = (image) => {
this.favorites.push(image);
this.data = null;
this.text = '';
};
}
decorate(Store, {
text: observable,
updateText: action,
data: observable,
searchImage: action,
setData: action,
//adding decorators
favorites: observable,
addToFavorite: action,
});
export default new Store();
Now we’ll update our View for these added observables and actions. We want to clear previously searched images and show the added favorite image, this can be done simply by:
// previous destructuring
const { favorite, addToFavorite} = this.props.store
return (
<>
{/* TextInput and Button added earlier */}
{/* If data is available then show search results otherwise show the favorite images */}
{data ?
<FlatList // To render list of images
style={styles.container}
data={data.results}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<ImageView
source={{ uri: item.urls.small }}
onPress={() => addToFavorite(item.urls.small)} // action to add url to favorite
/>
)}
/> :
<FlatList
style={styles.container}
data={favorites}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<ImageView
source={{ uri: item }} // render favorite images
/>
)}
/>
}
</>
);
We’ve used observers
, observables
and actions
so far. Let’s add a computed
to show the number of images added to favorites. computed
works like a getter function to get derived state from the observable. It can be added as:
import { decorate, observable, action, computed } from "mobx";
class Store {
// previously added value
get getFavoriteCount() {
return this.favorites.length;
}
}
decorate(Store, {
// previously added values
getFavoriteCount: computed,
});
export default new Store();
Let’s quickly add it to our View also:
const { getFavoriteCount } = this.props.store;
return (
// TextInput, Button
<Text style={styles.count}>
Images added: {getFavoriteCount}
</Text>
// FlatList
);
Now, the last thing we have to do is provide store
in Provider
to the root component. Our root file will look like this:
import React from 'react';
import ImageList from './src/container/ImageList';
// imports Provider and store
import { Provider } from 'mobx-react';
import store from './src/store';
const App = () => {
return (
<Provider store={store}>
<ImageList />
</Provider>
);
};
export default App;
That’s it. Let’s see a GIF of our app in action:
We’ve learned about observables
, actions
, observers
, and computed
properties in MobX and successfully used them by building a simple React Native app. I hope you had fun learning MobX and this tutorial was helpful in getting you started with React Native + MobX. Happy coding! 👨💻
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Thanks for the this article. Can you share github repo for this? There are few missing peaces and typos that are causing errors while implementing this.