US & Canada: 877 849 1850
International: +1 678 648 3113

Accelebrate Blog

ACCELERATED LEARNING, CELEBRATED RESULTS

State Management: MobX versus Redux

MobX is yet another entry into the state management space for user interface (UI) applications. Its biggest competitor is Redux (especially in the React space), and to a more limited extent, the numerous other Flux variants, and many other non-Flux approaches to managing state.

Managing application state is one of the most important aspects of creating UI applications:

  1. How state is stored
  2. How changes are tracked
  3. How those changes are efficiently applied to the UI

There are many approaches to all three and MobX focuses on solving the second one, how changes are tracked while integrating well with other parts of the applications.

Observables & Reactive Programming

So, what sets MobX apart from other state management solutions, particularly Redux? First, MobX uses reactive programming. While Redux can be coded in a reactive way (especially with the numerous middleware and wrapper libraries that exist), MobX is reactive from the ground up and forces UI coding using reactive principles. In this respect, MobX is more opinionated than Redux. One of the benefits of opinionated code is that it tends to be more concise, but less flexible.

To accomplish a reactive programming approach, MobX uses JavaScript classes with observable and computed properties. When observable properties update, updates to computed properties are triggered. Then the elements of the UI observing the observable and computed properties react to the changes by updating themselves. This observable and computed approach follows the philosophy of MobX which is:

Anything that can be derived from the application state,
should be derived. Automatically.

State information is stored on a plain JavaScript object but when the object changes, MobX updates various observable and computed values automatically. The UI then reacts.

Updates to the plain JavaScript object are accomplished using mutable programming techniques instead of immutable techniques. The use of mutable programming results in easier to implement and easier to understand code for most developers, especially those developers without immutable programming experience. So how is this different from Redux?

With Redux, state changes are always performed immutably. This enables developers to use object reference differences to detect state changes and selectively update the UI. While immutable programming is a powerful technique, and the code to support it is very explicit, it can be very verbose and requires developer discipline (if a developer does something mutably, the system could break. This would be hard to fix because locating the offending mutable code in a large code base can be difficult).

With MobX, the responsibility of tracking changes to the state is pushed off to MobX. MobX notifies the observable properties (and indirectly the computed properties) when changes occur. This makes it easier for the developer, but results in a lot of “magic.” It also requires its own set of rules for the developer to follow when coding MobX observable and computed properties. New developers love the “magic” because they can experience initial success with little effort. It is experienced developers who eschew the magic because they know the problems it can eventually yield with larger applications when the magic is not fully understood. With that said, properly understood magic can yield some great benefits, including significant developer productivity boosts.

Declarative-Style with Decorators

Unlike Redux, MobX requires the use of Babel to provide transpiling support for decorators. MobX uses decorators to mark class properties as observable and computed. Technically, decorators are not required, and non-decorator syntax approaches can be used to mark class properties, thereby removing the need for Babel. However decorators are what make coding with MobX cool. The ability to use a declarative syntax to mark up the model nicely complements the declarative approach to React component programming with JavaScript classes and JSX.

MobX Car Tool Application Source Code

class CarStore {
  @observable cars = initialCarData;

  @observable carForm = {
    make: '',
    model: '',
    year: 1900,
    color: '',
    price: 0,
  };

  @computed get inventoryTotal() {
    return this.cars.reduce( (acc, car) => acc + car.price, 0 );
  }

  // …more code…
}

Unidirectional Flow

One of the approaches that Redux (and all Flux variants) use to update the application’s state is to process all updates using actions. The purpose of actions is to have a single point where all potential changes to the state enter. Each store (or in the case of Redux, the single store) can process the action to update their state and then components are notified after each store has had a chance to process the action.

Unidirectional Flow of Redux

Figure: Unidirectional Flow of Redux
 Source: Training 4, Developers, Inc. Courseware

 

This approach, while very code intensive, is extremely effective at keeping everything synchronized between the state and application UI. Also, with each step being clearly defined and explicit, it’s very easy for the developer to follow the flow. The flow of updates to the state and the UI is unidirectional.

Redux Car Tool Application Source Code

const actionTypes = keyMirror({
  ADD_CAR: null,
});

const addCarActionCreator = car => ({
  type: actionTypes.ADD_CAR,
  car,
});

const reducer = (state= { cars: initialCarData }, action) => {

  switch (action.type) {
    case actionTypes.ADD_CAR:
      return { ...state, cars: [ ...state.cars, {
        id: Math.max( ...state.cars.map(c => c.id) ) + 1,
        ...action.car
      } ] };
    default:
     return state;
  }

};

To support this unidirectional approach, actions and their data requirements must be defined. This typically involves the creation of numerous action creator functions, support for asynchronous operations, etc. Additionally, the actions are processed using special reducer functions. Reducer functions are pure functions which typically operate on the state tree (or a portion of the state tree for larger applications) to produce a new state. Coding it is a lot of work. So how does MobX make this better?

Unidirectional Flow without Verbosity

With MobX the data flow is still unidirectional, but there is no need to define actions or the other scaffolding needed to support the processing of actions to update the state and the UI. Developers write more traditional code to update models. MobX is then responsible for processing the state updates and notifying the observable and computed properties.

Unidirectional Flow of MobX

­­­Figure : Unidirectional Flow of MobX
 Source: https://github.com/mobxjs/mobx, Accessed: 09/01/2017

 

Using MobX eliminates a lot of code, especially the boiler-plate code for which Redux (and Flux) are famous. The downside? The observable and computed properties need to be interacted with in certain ways or they can lose their magical powers. Knowing how to interact with them properly is key to successful MobX programming. It is very easy to write perfectly valid JavaScript code which breaks MobX. However, such semantics (except for immutable coding) do not apply to Redux. Also, many of the performance enhancements built into UI libraries like React (MobX is most commonly used with React and provides extensive documentation on how to integrate the two) rely upon immutable programming; therefore, those enhancements cannot necessarily be used with MobX.

MobX Car Tool Application Source Code

class CarStore {

  // … more code …

  addCar(car) {
    car.id = Math.max( ...this.cars.map(c => c.id) ) + 1;
    this.cars.push(car);
  }

  // … more code …
}

Integrating with React

Both MobX and Redux are completely independent of any specific UI framework or library. Nevertheless, they are both frequently paired with React and libraries that have been created to easily connect React with either state management library. For Redux, there is the react-redux NPM package and for MobX the mobx-react package. Both provide functionality for wrapping a React component to connect it to a Redux store or a MobX store, respectively.

Redux refers to the wrapped component as a container component. Inside the container component, the component that is wrapped is referred to as a presentation component. The container component is used to connect the presentation component to the store via mappings between the store’s state and the store’s bound action dispatch functions to the props of the presentational component. To wire this up, react-redux provides a connect function that accepts several arguments. This includes a mapping function to map the store state to the presentational component’s props, and a mapping function to map the action creators, and the store’s dispatch function to the presentational component’s props. The map state to props function runs on each store update, and the map dispatch to props function runs on the initial creation of the container only.

const store = createStore(reducer);

const mapStateToProps = ({ cars }) => ({
  cars,
  inventoryTotal: cars.reduce( (acc, cur) => acc + cur.price, 0 ),
});

const mapDispatchToProps = dispatch => bindActionCreators({ addCar: addCarActionCreator }, dispatch);

// … presentational component class code here …

const CarToolContainer = connect(mapStateToProps, mapDispatchToProps)(CarTool);

export const ReduxCarTool = () => <CarToolContainer store={store} />;

For MobX, the mobx-react package provides an observer function that returns a presentational component wrapped in an observer component. The observer component observes the store object instantiated from the JavaScript class with the MobX decorated properties. The MobX observer component is really a container component as it connects the presentational component to a MobX store. Following a good pattern of decoupling a presentational component from the application (and its state management), both MobX and Redux define a distinction between a container component and a presentation component.

const store = new CarStore();

const CarTool = props => {

  // … implementation of the presentational component …

};

const CarToolObserver = observer(CarTool);

export const MobXCarTool = () => <CarToolObserver store={store} />;

The big syntax difference between Redux and MobX is that mapping functions do not need to be defined for the MobX version. There are many reasons for this. Among them is that with MobX, multiple stores can be created. For this example, there is one store for the one component. However, with a large application, multiple stores can be created, potentially one store for each component. This means the store’s properties will be perfectly tailored for the presentational component, alleviating the need for a special mapping function to map the store’s state to the presentational component’s props. The map dispatch to props is not needed because MobX does not rely upon a call to the store’s dispatch function to propagate changes across the UI. The trigger of the change occurs through invoking a function which changes an observable and the magical propagation which flows from it, whereas the Redux approach is more explicit.

A Complete Example

The complete application written using MobX and Redux is available at https://github.com/training4developers/mobx_redux_blog_post. The GitHub repo has complete instructions for downloading the code, installing dependencies, and running the application. When the application is loaded, the MobX or Redux version can be selected from a drop down. Both applications possess the same functionality and serve as a good apples-to-apples comparison of MobX to Redux. The MobX code is shorter and the declarative-style of the observable and computed decorators makes for quicker reading of the code, but the magic hides some the details. The Redux code is longer and more explicit, however, all the details are plain to see. Additional developer features are available with Redux, such as time traveling, that do not exist with MobX.

Conclusion

Overall, I think the developer community still prefers Redux over MobX, but the MobX following is growing. Like any tool, it’s useful for certain sites and development teams, but perhaps not as useful for others. Just as Redux is not appropriate in every situation, neither is MobX. Nevertheless, the innovation and creativity of the JavaScript world ignites the spirit of discovery and learning that draws so many developers to programming in the first place. We can expect great things from both libraries – and even from libraries yet to be created!


Author: Eric Greene, one of Accelebrate’s instructor

Accelebrate offers private React/Redux training for groups and instructor-led online JavaScript classes for individuals.  Visit https://www.accelebrate.com/react-training to see the full list of courses.

Categories: JavaScript Articles
Tags: , , , , , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

Your email address will not be published. Required fields are marked *

*



You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Please contact us for GSA pricing.
Contract #GS-35F-0307T

Please see our complete list of
Microsoft Official Courses