About the article

The purpose of this article is to give an outline of the MobX library, describe what it is based on, and tell about the advantages of this tool for managing the state of an application.

In this article, we will share recommendations for using MobX, provide useful links to various resources and documentation for those people who want to explore this tool at greater length and in greater detail.

What is MobX?

MobX is a simple scalable solution intended to manage the state of an application. It is one of the most popular implementations of the Flux architecture. If you are not yet familiar with such a pattern for the development of frontend applications as Flux, we suggest you explore it here, since it all began with it.

The MobX library is used in conjunction with React, has multiple features intended for monitoring model changes, provides the possibility to determine computed properties, and implement functions that will respond to model changes.

The easy-to-read and concise syntax of the MobX library allows using good old javascript classes of the ES6 specification to implement the repository.

In addition, MobX contains built-in debugging tools to track the behavior of rendering and dependencies of your application model.

How to start using MobX

MobX installation

To install MobX, enter one of the following commands:

  1. Through Yarn — yarn add mobx.
  2. Through NPM — npm install –save mobx.

After that, the MobX package will be added to package.json and its components can be used in the project by importing:

import {observable, action, computed, makeObservable} from “mobx”.

Basic concepts

Let’s proceed directly to the description of some of the main features and functionality of MobX that we will work with.

Observable state

Properties, integer objects, arrays, Maps and Sets can be made observable. To make an object observable, you should specify an annotation for each property using makeObservable or makeAutoObservable.

makeObservable(target, annotations?, options?)

makeObservable is used to intercept existing properties of an object and make them observable. You can transfer any JavaScript object to the target. Usually, makeObservable is written in the class constructor and its first argument is this. The annotations matches annotations to each item. When specifying decorators (observable, action…), the @ symbol should be omitted. Example:

Example of specifying decorators (observable, action…)

makeAutoObservable(target, overrides?, options?)

The difference between makeAutoObservable and makeObservable is that makeAutoObservable outputs default properties. The overrides is used to override the behavior using annotations. We can also specify false to exclude a method or property from processing. makeAutoObservable may be more compact and easy to use than makeObservable because new members are not explicitly specified. But makeAutoObservable cannot be used in classes that have the super keyword or are subclasses.

@Observable

Using the @observable annotation, MobX monitors existing data structures. It defines the observable field in which the state is stored. Data can be defined as objects, arrays, or instances of classes. The state is the data that manages the application. This state must be constantly monitored. It can be changed, and if the state changes, you need to respond to these changes.

import { observable } from ‘mobx’;

class User {
  id = Math.random();
@observable name = ‘ ‘;
@observable age = ‘ ‘;
@observable hungry = false;
}

Note: annotations in javascript are not yet a standardized technology of the future ES.Next(ECMAScript) specification, and ES6+ (Babel) transpilers must be used to make them work in your application. But there is an alternative to this, which is described in the Mobx documentation.

Computed values

With the @computed annotations, you can define the data that will be automatically updated when other data changes. But this is done if the values are observable. If the values are not observable, they are all suspended.

Computed values are very similar to formulas in spreadsheets. They help reduce the amount of the state you have to store and are very optimized. Use them wherever possible.

Computed values can be created by annotating JavaScript getters using the @computed annotation.

import { observable, computed } from ‘mobx’;

class UserList {
@observable users = [];
@computed get hungryUsersCount() {
        return this.users.filter(todo => !todo.hungry).length;
}
}

In this code sample, the hungryUsersCount property will be updated, if the users observable array is changed.

Note: If you are unfamiliar with the get/set functions that are present in the example, you can study them here.

Reactions

Reactions in MobX are similar to @computed, as they also use the observable data of the repository inside themselves, but they do not return the value. Instead, they create a side effect.

They are triggered automatically each time you change the observable data to execute a specific logic that you will put in the reaction function. In this case, the reaction will be performed by changing only those data that depend on this particular reaction.

MobX has several reaction functions, autorun() and when() are among them.

The autorun function is the simplest one to be understood by the reaction, and in the example below, we implemented a simple output of the expression to the console in case of data change.

import { autorun } from ‘mobx’;

autorun(() => {
   console.log(user.name || user.age)
});

The when function is also a very interesting reaction, which allows checking data values before executing a certain logic.

class User {
   constructor () {
when(
() => this.hungry,
() => this.eatSandwich()
)
}
eatSandwich() {
       // sandwich eating logic
}
}

Reaction functions can accept an option object as the second argument, which can be used to configure the start delay, as well as configure the scheduler to determine how to restart the function and handle the error if it occurs.

Actions

Actions in MobX are a feature that is marked with the @action decorator. @action annotation should only be used for functions intended to change the state. Functions that retrieve information (search for or filter data) should not be marked as an action, so that MobX could track their calling. We can modify observable data within this function.

import { observable, action } from ‘mobx’;

class User {
@observable name = ‘ ‘;

@action setUserName(userName) {
        this.name = userName;
}
}

The code example demonstrates the setUserName method, which is an action. The method sets the user name, after which MobX takes care of the rest and notifies all listeners who depend on this field.

Actions are not always class methods, they can be located anywhere in the application, the main thing is that they are defined together with the @action annotation.

It is good practice in actions to make asynchronous ajax calls.

Additional information

MobX requires to declare your actions, although makeAutoObservable can automate most of this work. Actions help structure the code better and provide the following performance benefits:

  • They are executed inside transactions. No reactions will be executed until the most external action is completed. It ensures that the intermediate or incomplete values generated during the action are not visible to the rest of the application until the action is completed.
  • By default, it is not allowed to change the state outside of actions. This helps to clearly identify where exactly the state in the code base updates.

Example of using the action with makeObservable:

Example of using the action with makeObservable

How does it work?

After getting acquainted with the main concepts of MobX, it is worth summarizing and explaining briefly how they interact with each other:

  1. We have the state, a class that contains @observable data in the form of objects, arrays, primitives that form the model of our application.
  2. Next, we have the Actions that change the state and are called from the reactive React components of the application.
  3. After performing the Actions, MobX will make sure that all changes in the state of the application caused by the actions are automatically processed by all computed values (@computed) and reactions (autorun(), when()).
  4. MobX also triggers automatic reactions, which will perform input/output work, check and make sure that the interface of our application has been completely updated.

Recommendations, best practices

  • Modify @observable data only in the repository class.
    To change data, always implement the action special method in your repository and call it in the component.
  • Use computed values.
    Computed values created using @computed annotations are well optimized, cache data values on which they depend. MobX developers urge not to be afraid to use them. Highlighting small computations in the @computed function will allow them to be easily reused in other components and make the code more readable and convenient.
  • Use the @observer annotation for all of your components that display @observable data.
    The @observer annotation makes your components reactive so that @observable data changes that the component displays trigger the process of component redrawing. MobX developers recommend implementing more @observer components, which makes drawing more efficient.
  • Do not copy @observable data to local variables.
    If you save @observedable data to the local variable of a component, changes in this variable will not be tracked. If you want to simplify the code, MobX developers recommend defining a function in the @computed component that will return the value of @observable data.
  • Always delete reactions.
    The autorun, observe, and intercept functions return the delete function that will stop the reaction function when called. This is recommended when you no longer need the reaction function. This will allow the application memory to be cleared in time.
  • Do not implement asynchronous call functions in the repository class.
    Collect all asynchronous call functions into a separate class and transfer it to the application repository. This makes it easier for you to test the app and get more concise and readable code.
  • Implement the application’s business logic in repository classes.
    This approach will improve code organization and allow using the necessary methods again in other components.

MobX vs Redux. Advantages and disadvantages

Advantages

  • MobX requires less code to implement state management as compared to Redux.
  • MobX is well optimized and in most cases you will not need to implement additional optimization.
  • MobX allows using object-oriented programming principles and implementing data as classes instead of simple functions.
  • MobX is convenient to use when you have to update the properties of large data constructs with deep nesting, while with Redux you will need to write much more code.
  • MobX is useful when you need your app components to link to each other and have access to parent objects. For example, when implementing tree structures using recursion.

Disadvantages

  • MobX gives developers too much freedom to write code, and it is very important in a large team to immediately identify common approaches in which state management will be implemented.
  • MobX has less explicit upgrade logic and most magic is hidden inside the library.
  • MobX is difficult to debug.

Conclusion

MobX is an excellent tool for application state management with its own features and advantages, which has gained popularity among frontend developers.

Useful links

MobX — MobX official documentation.
Awesome MobX — an official collection of the most popular and essential things related to MobX. Here you will find useful articles and video tutorials for studying Mobx, examples of projects using MobX, templates of the initial structure of a frontend project to start development quickly.
Awesome-MobX — an unofficial collection of essentials for MobX.
MobX-react-boilerplate — a simple minimal application that uses MobX with React.
MobX-react-todomvc — an example of a slightly more complex application, a ToDo list using MobX.