Tutorial

MobX with React Native, Simplified

Published on December 7, 2019
Default avatar

By Shad Mirza

MobX with React Native, Simplified

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.

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?

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 = () => {
    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 = () => {
    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.

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(() => {
  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...
      () => 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.

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.
  2. 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(
  () => 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

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
  2. Creating a View that will observe for state changes
  3. 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-<your-os>

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

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

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

  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:

React Native app demo animated GIF


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.

Learn more about us


About the authors
Default avatar
Shad Mirza

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


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.

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel