MobX Quick Start Guide

4.3 (7 reviews total)
By Pavan Podila , Michel Weststrate
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies

About this book

MobX is a simple and highly scalable state management library in JavaScript. Its abstractions can help you manage state in small to extremely large applications. However, if you are just starting out, it is essential to have a guide that can help you take the first steps. This book aims to be that guide that will equip you with the skills needed to use MobX and effectively handle the state management aspects of your application.

You will first learn about observables, actions, and reactions: the core concepts of MobX. To see how MobX really shines and simplifies state management, you'll work through some real-world use cases. Building on these core concepts and use cases, you will learn about advanced MobX, its APIs, and libraries that extend MobX.

By the end of this book, you will not only have a solid conceptual understanding of MobX, but also practical experience. You will gain the confidence to tackle many of the common state management problems in your own projects.

Publication date:
July 2018
Publisher
Packt
Pages
236
ISBN
9781789344837

 

Chapter 1. Introduction to State Management

The heart of your React app lives in the client state (data) and is rendered via React components. Managing this state can become tricky as you tackle user interactions (UI), perform async operations, and handle domain logic. In this chapter, we will start with a conceptual model of state management in UI, the role of side effects, and the flow of data.

Then, we will take a quick tour of MobX and introduce its core concepts. These concepts will help in drawing some comparisons with Redux. You will see that MobX turns out to be a more declarative form of Redux!

The topics covered in this chapter are as follows:

  • What is the client state?
  • The side effect model
  • A speed tour of MobX

 

 

The client state


The UI that you can see and manipulate on screen is the result of painting a visual representation of data. The shape of data hints at the kind of controls you provide for visualizing and manipulating this data. For example, if you have a list of items, you will likely show a List control that has an array of ListItems. Operations may include searching, paginating, filtering, sorting, or grouping the items in the list. The state of these operations is also captured as data and informs the visual representation.

The following diagram shows the direct relationship of an array with a List control:

In short, it is the data that takes on a pivotal role in describing the UI. Handling the structure and managing the changes that can happen to this data is what we commonly refer to as state management. State is just a synonym for the client-data that is rendered on the UI.

Note

State management is the act of defining the shape of data and the operations that are used to manipulate it. In the context of the UI, it is called client-side state management.

As the complexity of the UI increases, more state is accumulated on the client. It gets to a point where state becomes the ultimate source of truth for whatever we see on the screen. This approach to UI development, where we elevate the importance of the client-state, has been one of the biggest shifts in the frontend world. There is an interesting equation that captures this relationship between UI and state:

fn is a transformation function that is applied on the state (the data) that produces a corresponding UI. In fact, a subtle meaning that is hidden here is that, given the same state, fn always produces the same UI.

In the context of React, the preceding equation can be written as follows:

The only difference here is that fn takes two inputs, props and state, which is the prescribed contract of a React component.

Handling changes in state

However, the preceding equation is only giving half the story of a UI. It's true that the visual representation is derived from the state (through the transformation function, fn), but it does not account for the user operations that occur on the UI. It's like we have completely ignored the user in the equation. After all, the interface is not just used to visually represent data (state), but to also allow the manipulation of that data.

This is where we need to introduce the concept of actions that represent these user operations, which results in a change in state. Actions are the commands that you invoke as a result of various input-events that are fired. These actions cause a change in the state, which is then reflected back on the UI.

We can visualize the triad of State, UI, and Actions in the following figure:

It is worth noting that the UI does not change the state directly, but instead does it via a message-passing system by firing actions. The action encapsulates the parameters that are required to cause the appropriate change in state. The UI is responsible for capturing various kinds of user events (clicks, keyboard presses, touches, voice, and so on) and translating them into one or more actions that are then fired to change the state.

When the State changes, it notifies all of its observers (subscribers) of the change. The UI is also one of the most important subscribers that is notified. When that happens, it re-renders and updates to the new state. This system of data flow from the State into the UI is always uni-directional and has become the cornerstone of state management in modern UI development.

One of the biggest benefits of this approach is that it becomes easy to grasp how the UI is kept in sync with changing data. It also cleanly separates the responsibilities between rendering and data changes. The React framework has really embraced this uni-directional data flow and you will see this adopted and extended in MobX as well.

 

The side effect model


Now that we understand the roles of UI, state, and actions, we can extend this to build a mental model of how a UI needs to operate. Reflecting back on the triad of Action --> State --> UI, we can make some interesting observations that are not clearly answered. Let's think about how we would handle operations such as the following:

  • Downloading data from a server
  • Persisting data back on the server
  • Running a timer and doing something periodically
  • Executing some validation logic when some state changes

These are things that don't fit nicely in our data flow triad. Clearly, we are missing something here, right? You might argue that you could put these operations inside the UI itself and fire actions at certain times. However, that would tack on additional responsibilities to the UI, complicating its operation and also making it difficult to test. From a more academic perspective, it would also violate theSingle Responsibility Principle(SRP). SRP states that a class or a module should have only one reason to change. If we start handling additional operations in the UI, it would have more than one reason to change. 

So, it seems like we have some opposing forces in action here. We want to retain the purity of the data flow triad, handle ancillary operations such as the ones mentioned in the preceding list, and not add extra responsibilities to the UI. To balance all of these forces, we need to think about the ancillary operations as something external to the data flow triad. We call these side effects.

Side effects are a result of some state-change and are invoked by responding to the notifications coming from the state. Just like the UI, there is a handler, which we can call the side effect handler, that observes (subscribes to) the state change notifications. When a matching state change happens, the corresponding side effect is invoked:

There can be many side effect handlers in the system, and each of them is an observer of the state. When a part of the state they are observing changes, they will invoke the corresponding side effects. Now, these side effects can also cause a change in state by firing additional actions.

As an example, you could fire an action from the UI to download some data. This results in a state change to some flag, which results in notifications being fired to all the observers. A side effect handler that is observing the flag will see the change and trigger a network call to download data. When the download completes, it will fire an action to update the state with the new data.

The fact that side effects can also fire actions to update the state is an important detail that helps in completing the loop around managing state. So, it's not just the UI that can cause state changes, but also external operations (via side effects) that can affect a state change. This is the mental model of side effects, which can be used to develop your UI and manage the state that it renders. The model is quite powerful and scales very well over time. Later in this chapter and also throughout this book, you will see how MobX makes this side effect model a reality and fun to use.

With these concepts in mind, we are now ready to enter the world of MobX.

 

A speed tour of MobX


MobX is a reactive state management library that makes it easy to adopt the side effect model. Many of the concepts in MobX directly mirror the terminology we encountered earlier. Let's take a quick tour of these building blocks.

An observable state

The state is at the epicenter of all things happening in the UI. MobX provides a core building block, called the observable, that represents the reactive state of your application. Any JavaScript object can be used to create an observable. We can use the aptly named observable() API as follows:

import {observable} from 'mobx';

let cart = observable({
    itemCount: 0,
    modified: new Date()
});

In the preceding example, we have created a simple cart object that is also an observable. The observable() API comes from the mobx NPM package. With this simple declaration of an observable, we now have a reactive cart that keeps track of changes happening on any of its properties: itemCount and modified.

Observing the state changes

Observables alone cannot make an interesting system. We also need their counterparts, the observers. MobX gives you three different kinds of observers, each tailor-made for the use cases you will encounter in your application. The core observers are autorun, reaction, and when. We will look at each of them in more detail in the next chapter, but let's introduce autorun for now.

The autorun API takes a function as its input and executes it immediately. It also keeps track of the observables that are used inside the passed-in function. When these tracked observables change, the function is re-executed. What is really beautiful and elegant about this simple setup is that there is no extra work required to track observables and subscribe to any changes. It all just happens automatically. It's not magic, per se, but definitely an intelligent system at work, which we will cover in a later section:

import {observable, autorun} from 'mobx';

let cart = observable({
itemCount: 0,
modified: new Date()
});

autorun(() => {
console.log(`The Cart contains ${cart.itemCount} item(s).`);
});

cart.itemCount++;

// Console output:
The Cart contains 0 item(s).
The Cart contains 1 item(s).

In the preceding example, the arrow-function that was passed into autorun is executed for the first time and also when itemCount is incremented. This results in two console logs being printed. autorun makes the passed-in function (the tracking-function) an observer of the observables it references. In our case, cart.itemCount was being observed and when it was incremented, the tracking function was automatically notified, resulting in the console logs getting printed.

It's time to take action

Although we are mutating cart.itemCount directly, it is definitely not the recommended approach. Remember that the state should not be changed directly and instead should be done via actions. The use of an action also adds vocabulary to the operations that can be performed on the observable state.

In our case, we can call the state mutation that we were doing as an incrementCount action. Let's use the MobX action API to encapsulate the mutation:

import { observable, autorun, action } from 'mobx';

let cart = observable({
itemCount: 0,
modified: new Date(),
});

autorun(() => {
console.log(`The Cart contains ${cart.itemCount} item(s).`);
});

const incrementCount = action(() => {
    cart.itemCount++;
});

incrementCount();

The action API takes a function that will be called whenever the action is invoked. It may seem superfluous that we are passing a function into action, when we could just wrap the mutation inside a plain function and call the plain function instead. An astute thought, for sure. Well, there is a good reason for that. Internally, action is doing much more than being a simple wrapper. It ensures that all notifications for state changes are fired, but only after the completion of the action function.

When you are modifying a lot of observables inside your action, you don't want to be notified about every little change immediately. Instead, you want to be able to wait for all changes to complete and then fire the notifications. This makes the system more performant and also reduces the noise of too many notifications, too soon.

Going back to our example, we can see that wrapping it in an action also improves the readability of the code. By giving a specific name to the action (incrementCount) we have added vocabulary to our domain. In doing so, we can abstract the details of what is needed to actually increment the count.

Observables, observers, and actions are at the core of MobX. With these fundamental concepts, we can build some of the most powerful and complex React applications.

Note

In the MobX literature, side effects are also called reactions. Unlike actions that cause state changes, reactions are the ones responding to state changes.

Note the striking similarity with the uni-directional data flow that we saw earlier. Observables capture the state of your application. Observers (also called reactions) include both the side effect handlers as well as the UI. The actions are, well, actions that cause a change in the observable state:

A comparison with Redux

If we were to talk about state management in React and we didn't mention Redux, it would be a complete remiss. Redux is an extremely popular state management library anditspopularity stems from the fact that it simplified the original Flux architecture that was proposed by Facebook. It got rid of certain actors in Flux,such as dispatchers, which resulted in combining all the stores into one, commonly referredtoas thesingle state tree.

Note

In this section, we will do a head-on comparison with another state management library called Redux. If you have not used Redux before, you can certainly skip this section and move on to this chapter's summary.

MobX has some conceptual similarities with Redux as far as the data flow is concerned, but that is also where the similarities end. The mechanism adopted by MobX is drastically different than the one taken by Redux. Let's have a brief overview of Redux before we get into a slightly deeper-level comparison.

Redux in a nutshell

The data flow triad that we saw earlier is also applicable to Redux in its entirety. It's in the state update mechanism that Redux adds its own twist. This can be seen in the following figure:

When the UI fires the action, it is dispatched on the store. Inside the store, the action first passes through one or more middleware, where it can get acted upon and swallowed without further propagation. If the action passes through the middleware, it is sent to one or more reducers, where it can be processed to produce a new state of the store.

This new state of the store is notified to all of the subscribers, with the UI being one of them. If the state is different from the previous value that the UI has, the UI is re-rendered and brought in sync with the new state.

There are few things here that are worth highlighting:

  • From the point where the action enters the store, until the new state is computed, the entire process is synchronous.
  • Reducers are pure functions that take in the action and the previous state and produce the new state. Since they are pure functions, you cannot put side effects, such as a network call, inside a reducer.
  • Middleware is the only place where side effects can be performed, eventually resulting in an action being dispatched on the store.

If you are using Redux with React, which is the most likely combination, there is a utility library called react-redux, which can glue the store with React components. It does this through a function called connect(), which binds the store with the passed in React component. Inside connect(), the React component subscribes to the store for state-change notifications. Binding to the store via connect() means that every state change is notified to every component. This requires adding additional abstractions, such as a state-selector (using mapStateToProps) or implementing shouldComponentUpdate() to receive only the relevant state updates:

connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(Component)

We are deliberately skipping over a few other details that are required for a complete React-Redux setup, but the essentials are in place for a deeper comparison of Redux with MobX.

MobX versus Redux

In principle, MobX and Redux accomplish the same goal of providing a uni-directional data flow. The store is the central actor that manages all state changes and notifies the UI and other observers of the change in state. The mechanism to achieve that is quite different between MobX and Redux.

Redux relies on immutable state snapshots and reference-comparisons between two state snapshots to check for changes. In contrast, MobX thrives on mutable state and uses a granular notification system to track state changes. This fundamental difference in approach has implications on the Developer eXperience (DX) of using each framework. We will use the DX of building a single feature to perform a MobX versus Redux comparison.

Let's start with Redux first. Here is the list of things you have to do when working with Redux:

  • Define the shape of the state tree that will be encapsulated in the store. This is normally called initialState.
  • Identify all actions that can be performed to change this state tree. Each action is defined in the form { type: string, payload: any }. The type property is used to identify the action and payload is additional data that is carried along with the action. The action types are usually created as string constants and exported from a module.
  • Defining raw actions every time you need to dispatch them becomes very verbose. Instead, the convention is to have an action-creator function that wraps the details of the action type and takes in the payload as an argument.
  • Use the connect method to wire the React component with the store. Since every state change is notified to every component, you have to be careful to not re-render your component unnecessarily. The render should only happen when the part of the state that the component actually renders has changed (via mapStateToProps). Since every state change is notified to all connected components, it might be expensive to compute mapStateToProps every single time. To minimize these computations, it is recommended to use state selector libraries such as reselect. This increases the effort required to properly set up a performant React component. If you don't use these libraries, you have to take the onus of writing an efficient shouldComponentUpdate hook for the React component.
  • Inside every reducer, you have to make sure that you are always returning a new instance of the state anytime there is a change. Note that the reducers are usually kept separate from the initialState definition and that requires going back and forth to ensure the proper state is changed in each of the reducer actions.
  • Any side effect you want to perform has to be wrapped in middleware. For more complex side effects which involve async operations, it is better to rely on dedicated middleware libraries, such as redux-thunk, redux-saga, or redux-observables. Note that this also complicates how side effects are constructed and executed. Each of the previously mentioned middleware have their own conventions and terminology. Additionally, the place where an action is dispatched is not co-located with the place where the actual side effect is handled. This results in more jumping around files to construct the mental model of how a feature is put together.
  • As the complexity of the feature increases, there is more fragmentation between actions, action-creators, middlewares, reducers, and initialState. Not having things co-located also increases the effort needed to develop a crisp mental model of a how a feature is put together.

In the MobX world, the developer experience is quite different. You will see more of this as we explore MobX throughout this book, but here is the top-level scoop:

  • Define the observable state for the feature in a store class. The various properties that can be changed and should be observed are marked with the observable API.
  • Define actions that will be needed to mutate the observable state.
  • Define all of the side effects (autorun, reaction and when) within the same feature class. The co-location of actions, reactions, and the observable state keeps the mental model crisp. MobX also supports async state updates out of the box, so no additional middleware libraries are needed to manage it.
  • Use the mobx-react package that includes the observer API, which allows the React components to connect to the observable store. You can sprinkle observer components throughout your React component tree, which is in fact the recommended approach to fine-tune component updates.
  • The advantage of using the observer is that there is no extra work needed to make the component efficient. Internally, the observer API ensures that the component is only updated when the rendered observable state changes.

MobX shifts your mindset to think of the observable state and the corresponding React components. You don't have to focus much on the wiring needed to achieve this. It is abstracted away behind simple and elegant APIs, such as observable, action, autorun, and observer.

We can go as far as saying that MobX enables a more declarative form of Redux. There are no action creators, reducers, or middleware to handle actions and produce the new state. The actions, side effects (reactions), and observable state are co-located inside the class or module. There are no complex connect() methods to glue a React component to the store. A simple observer() does the job with no extra wiring.

Note

MobX is declarative Redux. It takes the workflow associated with Redux and simplifies it considerably. Some explicit setup is no longer needed, such as the use of connect() in Container components, reselect for memoized state selection, actions, reducers and of course the middleware.

 

Summary


The UI is the visual equivalent of data (the state) along with interactive controls to change that state. The UI fires actions, which leads to the change in state. Side effects are external operations that are triggered due to some state change. There are observers in the system that look out for certain state changes and perform the corresponding side effects.

The data flow triad of Action --> State --> UI, coupled with side effects, creates a simple mental model of the UI. MobX strongly adheres to this mental model and you can see that reflected in its API, with observables, actions, reactions, and observers. The simplicity of this API makes it easy to tackle some of the complex interactions in UI.

If you have used Redux before, you can see that MobX reduces the ceremony needed to cause a state change and handle side effects. MobX strives to provide a declarative and reactive API for state management without compromising on simplicity. Throughout this book, this philosophy of MobX will be explored, with a deeper look at its API and real-world use cases.

In the next chapter, we will dive into the world of MobX with a first hand look at its core building blocks.

About the Authors

  • Pavan Podila

    Pavan Podila has been building frontend applications since 2001 and has used a variety of tools, technologies, and platforms, from Java Swing, WPF with .Net/C#, Cocoa on macOS and iOS, to the web platform with frameworks like React and Angular. He has been working with React since 2013 and MobX since 2016. He is a colead of the Interactive Practice at Publicis.Sapient, where he builds large financial apps for web and mobile platforms.

    Earlier, he was a Microsoft MVP for client application development (2008-2011), a published author of WPF Control Development Unleashed (Addison-Wesley). He created QuickLens, a Mac App for UI designers/developers, and authored several articles and video courses on Tuts+.

    Pavan is a Google Developer Expert (GDE) for web technologies and currently authors courses on The UI Dev. He is a regular speaker at meetups, conferences, and workshops. When time permits, you can find him sketching on iPad or playing Ping-Pong.

    Browse publications by this author
  • Michel Weststrate

    Michel Weststrate (Msc) is tech lead and open source evangelist at Mendix. He has been active as both a frontend and backend developer in different stacks. An occasional speaker at software conferences, he has authored video courses on egghead.

    Intrigued by several (transparent) reactive programming libraries, he researched and worked on making the ReactJS framework more reactive while addressing predictability and maintainability constraints in other solutions. This led to mobservable (nowadays MobX), which was quickly adopted at Mendix.

    He's very active in the open source software community, and he authored MobX, Immer, and several small libraries, and coauthored MobX-state-tree.

    Browse publications by this author

Latest Reviews

(7 reviews total)
I like that e-books are not tied to an app like Kindle or so. You pay, download files and read in app of your preference.
Very good book, detailed explanation
Fast and as expected****.

Recommended For You

React Design Patterns and Best Practices - Second Edition

Build modular React web apps that are scalable, maintainable and powerful using design patterns and insightful practices

By Carlos Santana Roldán
Learn React with TypeScript 3

Start developing modern day component based web apps using React 16, Redux and TypeScript 3 with this easy to follow guide filled with practical examples.

By Carl Rippon
React Material-UI Cookbook

Develop modern-day applications by implementing Material Design principles in React using Material-UI

By Adam Boduch
Learn React Hooks

Create large-scale web applications with code that is extensible and easy to understand using React Hooks

By Daniel Bugl