URL: https://www.progressiverobot.com/react-mobx-react-native-simplified/

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.

What is State Management?

react illustration for: What is State Management?

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.

What is 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."*

Core concept

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

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();

				
			

Computed Observables

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 = () =&gt; {

 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

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 = () =&gt; {

 return 'The train is delayed by' + this.count;

 };



 @action

 updateDelay = delay =&gt; {

 this.count = delay;

 };

}



export default new Store();

				
			

Note that all state modifications must be done by actions only.

Reactions

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<^>.

1. autorun

autorun is simply a function that runs every time the state changes.

				
					
autorun(() =&gt; {

 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.

2. when

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...

 () =&gt; this.count &gt; 60,

 // ... then

 () =&gt; 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.

3. reaction

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:

  1. The first argument (the data function) watches for changes in data and returns data that is used as input for the second argument, the effect function.
  1. The second function accepts the data received by the first function as the first argument and perform side effects but only when the data function returns a new value. It also receives a second argument which can be used to dispose of the reaction during execution.

The following example shows a reaction that is invoked only once.

				
					
const reactionDemo = reaction(

 () =&gt; this.count,

 (count, reaction) =&gt; {

 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

				
			

MobX in Action

We'll understand the working of MobX by creating a React Native app in three simple steps:

  1. Defining state and making it observable
  1. Creating a View that will observe for state changes
  1. Modifying the state using actions

What We Are Building

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:

  1. Create a React Native application
				
					
$ react-native init UnsplashDemo

				
			

or, using Expo:

				
					
$ expo init UnsplashDemo

				
			
  1. Add MobX
				
					
$ npm install mobx mobx-react

				
			
  1. Run the project
				
					
$ react-native run-&lt;your-os&gt;

				
			

Or, using Expo:

				
					
$ expo start

				
			

Step 1. Defining State and Making it Observable

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) =&gt; {

 this.text = text;

 }



 // observable to save image response from api

 data = null;



 // action to call API and search images

 searchImages = () =&gt; {

 fetch(`https://api.unsplash.com/search/photos?client_id=${API_KEY}&amp;page=1&amp;query=${this.text}&amp;orientation=landscape`)

 .then(response =&gt; response.json())

 .then(data =&gt; this.setData(data));

 };



 // observables can be modifies by an action only

 setData = (data) =&gt; {

 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();

				
			

Step 2. Creating a View that Will Observe for State Changes

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.

  1. Boilerplate for ImageList component:
				
					
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 (

 &lt;&gt;

 &lt;TextInput // TextInput to get search query from user

 style={styles.input} 

 value={text}

 onChangeText={updateText}

 /&gt;

 &lt;Button // Button to call API

 title="Search"

 style={styles.button}

 onPress={searchImages}

 /&gt;

 /&gt;

 &lt;FlatList 

 data={data.results} // response from API

 keyExtractor={(item) =&gt; item.id}

 renderItem={({ item }) =&gt; (

 &lt;ImageView // reusable component to render image

 source={{ uri: item.urls.small }} // passing the url

 onPress={() =&gt; {}} // action to add item to favorite

 /&gt;

 )}

 /&gt;

 &lt;/&gt;

 );

}



// 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.

Step 3. Modifying the State Using Actions

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) =&gt; {...};



 data = null;

 searchImages = () =&gt; {...};



 setData = (data) =&gt; {...};



 // array to save favourite images

 favorites = [];



 // action to add images to favorites

 addToFavorite = (image) =&gt; {

 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 (

 &lt;&gt;

 {/* TextInput and Button added earlier */}

 {/* If data is available then show search results otherwise show the favorite images */}

 {data ?

 &lt;FlatList // To render list of images

 style={styles.container}

 data={data.results}

 keyExtractor={(item) =&gt; item.id}

 renderItem={({ item }) =&gt; (

 &lt;ImageView

 source={{ uri: item.urls.small }}

 onPress={() =&gt; addToFavorite(item.urls.small)} // action to add url to favorite

 /&gt;

 )}

 /&gt; :

 &lt;FlatList

 style={styles.container}

 data={favorites}

 keyExtractor={(item, index) =&gt; index.toString()}

 renderItem={({ item }) =&gt; (

 &lt;ImageView

 source={{ uri: item }} // render favorite images

 /&gt;

 )}

 /&gt;

 }

 &lt;/&gt;

);

				
			

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

 &lt;Text style={styles.count}&gt;

 Images added: {getFavoriteCount}

 &lt;/Text&gt;

 // 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 = () =&gt; {

 return (

 &lt;Provider store={store}&gt;

 &lt;ImageList /&gt;

 &lt;/Provider&gt;

 );

};



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! 👨‍💻