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:
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.
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.
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… }
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.
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?
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.
Figure : Unidirectional Flow of MobX
Source: https://github.com/mobxjs/mobx
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 … }
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.
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.
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!
Accelebrate offers private React/Redux training for groups and instructor-led online JavaScript classes for individuals. Visit /react-training to see the full list of courses.
Written by Eric Greene
Eric is a professional software developer specializing in HTML, CSS, and JavaScript technologies. He has been developing software and delivering training classes for nearly 19 years. He holds the MCSD Certification for ASP.Net Web Applications, and is a Microsoft Certified Trainer.
We offer private, customized training for 3 or more people at your site or online.
Our live, instructor-led lectures are far more effective than pre-recorded classes
If your team is not 100% satisfied with your training, we do what's necessary to make it right
Whether you are at home or in the office, we make learning interactive and engaging
We accept check, ACH/EFT, major credit cards, and most purchase orders
Alabama
Birmingham
Huntsville
Montgomery
Alaska
Anchorage
Arizona
Phoenix
Tucson
Arkansas
Fayetteville
Little Rock
California
Los Angeles
Oakland
Orange County
Sacramento
San Diego
San Francisco
San Jose
Colorado
Boulder
Colorado Springs
Denver
Connecticut
Hartford
DC
Washington
Florida
Fort Lauderdale
Jacksonville
Miami
Orlando
Tampa
Georgia
Atlanta
Augusta
Savannah
Hawaii
Honolulu
Idaho
Boise
Illinois
Chicago
Indiana
Indianapolis
Iowa
Cedar Rapids
Des Moines
Kansas
Wichita
Kentucky
Lexington
Louisville
Louisiana
New Orleans
Maine
Portland
Maryland
Annapolis
Baltimore
Frederick
Hagerstown
Massachusetts
Boston
Cambridge
Springfield
Michigan
Ann Arbor
Detroit
Grand Rapids
Minnesota
Minneapolis
Saint Paul
Mississippi
Jackson
Missouri
Kansas City
St. Louis
Nebraska
Lincoln
Omaha
Nevada
Las Vegas
Reno
New Jersey
Princeton
New Mexico
Albuquerque
New York
Albany
Buffalo
New York City
White Plains
North Carolina
Charlotte
Durham
Raleigh
Ohio
Akron
Canton
Cincinnati
Cleveland
Columbus
Dayton
Oklahoma
Oklahoma City
Tulsa
Oregon
Portland
Pennsylvania
Philadelphia
Pittsburgh
Rhode Island
Providence
South Carolina
Charleston
Columbia
Greenville
Tennessee
Knoxville
Memphis
Nashville
Texas
Austin
Dallas
El Paso
Houston
San Antonio
Utah
Salt Lake City
Virginia
Alexandria
Arlington
Norfolk
Richmond
Washington
Seattle
Tacoma
West Virginia
Charleston
Wisconsin
Madison
Milwaukee
Alberta
Calgary
Edmonton
British Columbia
Vancouver
Manitoba
Winnipeg
Nova Scotia
Halifax
Ontario
Ottawa
Toronto
Quebec
Montreal
Puerto Rico
San Juan