Introduction to Redux and Mobx
In my earlier post, I compared the performance and memory profiles of a benchmark application written in AngularJS, React/Redux and React/Mobx. It’s quite obvious from the metrics that React with Redux or Mobx gives significant performance gains compared to AngularJS. In this post, I will go over the core concepts, benefits and gotchas with both the libraries.
All the below code snippets are from the ticker dashboard application from my earlier post.
Redux
Core Concepts
http://redux.js.org/docs/introduction/ThreePrinciples.html
Single source of truth (UI State Tree)
The state of your whole application is stored in an object tree within a single store.
Here is the UI state tree for the stock ticker dashboard application.
Actions
The only way to change the state is to emit an action, an object describing what happened.
Action creator is a function returning the action object. In the below example, each Action is represented with a ‘type’ and ‘payload’.
Reducers
To specify how the state tree is transformed by actions, you write pure reducers.
Reducers are pure functions that modify the state in immutable way (returns new objects instead of mutating the object). Reducers shouldn’t cause side effects like making external api calls, triggering route changes.
If we design the action interfaces to have a common property (‘type’ in this example), we can leverage TypeScript’s discriminated unions to get type checking as well as the intellisense support in each case statement.
Connected and Pure (Presentational) UI Components
React-Redux library provides a Higher order React component which automatically listens for store.subscribe(listener)
events. When an action is dispatched to the store, Redux will notify the state changed event to all subscribers. All “React-Redux” connected components are subscribers to the Redux store and every connected component’s mapStateToProps method is executed with every state change. This method helps you to slice the UI state tree and pick the data needed for the specific component tree.
Pure components are normal React components. They read data from props to render and execute the callbacks sent via props. They are not aware of Redux or UI state tree.
Here is how the component hierarchy would look:
Here is an example of the connected component. TickerTile component renders stock ticker details (ticker, company name, price, sma, volume etc) and depends on the tickerData as a prop. The parent component just sends the tickerId (e.g. MSFT) as prop (termed as ownprops). The Higher Order connect component takes mapStateToProps mapper function which takes the tickerId and returns tickerData from UI state’s tickerHash and tickerId.
Here is an example of the pure component:
Pros
- Everything with Redux is very explicit without any magic.
- React’s deterministic state changes and view renders are great for testability and easy to debug any issues.
- Redux patterns force developers to think hard on the schema of the UI state tree.
- Patterns will be consistent across the codebase (at the cost of verboseness).
- UI would simply become the representation of the state tree. Changes to the State tree can be easily traced/monitored as the actions play out.
- Great Documentation and excellent community support.
Few things to watch out for
Designing UI State tree
- Make sure your UI state tree is normalized and try to keep it as flat as possible (instead of deeply nested structures). At times, deciding whether a property should go into the UI state or not can be confusing. Using local component state is fine as long as other parts of the application do not care about it.
Helpful links:
- http://redux.js.org/docs/faq/OrganizingState.html#do-i-have-to-put-all-my-state-into-redux-should-i-ever-use-reacts-setstate
- https://spin.atomicobject.com/2017/06/07/react-state-vs-redux-state/
Connected Components (containers)
mapStateToProps method of all connected components will be executed with every state changed event of the Redux store. Optimizing this method is one of the critical steps to get optimal performance in complex applications. If you need to execute expensive operations like deriving computed data from the normalized state tree, use Redux reselect library. It memoizes the result and skips recalculating unless input references change (there is a gotcha if you’re trying to reuse the selectors in multiple components).
If there are minimal connected components, props need to be propagated several levels down of the component hierarchy which is clearly not ideal in the large applications. Redux used to recommend connecting few components to the Redux store. Now, the recommendation is to use as many as you need.
In the stock ticker dashboard application from my previous post, In the updates test scenario, with 1500 tickerTile (connected) components in the view, Redux refreshed the price/volume changes within 6ms.
These two PRs (authored by Dan Abramov, creator of Redux) achieved substantial performance gains by connecting several components to Redux store and by optimizing mapStateToProps.
- Redux vs mobx — https://github.com/mweststrate/redux-todomvc/pull/1
- PixelPaint — https://github.com/dtinth/pixelpaint/pull/1
Helpful Link(s):
Batch dispatch calls
Anytime Redux’s dispatch method is called with an action, Redux executes all reducers, updates the state and notifies all the subscribers synchronously. React-Redux will then trigger mapDispathToProps on all the connected components.
If you’re updating different sections of the state tree through multiple actions at the same time, try to batch them to trigger only one notification.
Helpful Links:
https://github.com/markerikson/redux-ecosystem-links/blob/master/store.md#store-change-subscriptions
https://github.com/reactjs/redux/issues/2108
http://blog.isquaredsoftware.com/2017/01/idiomatic-redux-thoughts-on-thunks-sagas-abstraction-and-reusability/#multiple-dispatching
Redux Libraries
Redux is a tiny state management library (with minimal API) which acts as a building block for the higher level constructs. You need to bring in a lot of libraries to put together any real world application. It might be overwhelming at first (esp. for folks coming from AngularJS) but most of these libraries are small and come with good documentation.
https://github.com/reactjs/reselect
https://github.com/paularmstrong/normalizr
https://github.com/omnidan/redux-ignore
https://github.com/gaearon/redux-thunk
https://github.com/evgenyrodionov/redux-logger
https://github.com/tshelburne/redux-batched-actions
https://github.com/tappleby/redux-batched-subscribe
Functional Programming
Redux uses several functional programming patterns (currying, higher order functions, composition etc.). The patterns and code might look strange for folks coming from object oriented design background. But Redux comes with a great documentation and there are a ton of great articles/videos out there to gain familiarity with the patterns.
Mobx
From, https://mobx.js.org/
Both React and MobX provide very optimal and unique solutions to common problems in application development. React provides mechanisms to optimally render UI by using a virtual DOM that reduces the number of costly DOM mutations. MobX provides mechanisms to optimally synchronize application state with your React components by using a reactive virtual dependency state graph that is only updated when strictly needed and is never stale.
Core Concepts
https://mobx.js.org/intro/concepts.html
Observable State
MobX adds observable capabilities to existing data structures like objects, arrays and class instances.
Derivations
Anything that can be derived from the state without any further interaction is a derivation (user interface, computed values).
OR
/* a function that observes the state */
autorun(function() {
console.log("Total Tickers",
tickerDataModel.getAllTickers().length
);
});
Actions
Actions are functions that modify state.
All Derivations are updated automatically and atomically when the state changes. As a result, it is never possible to observe intermediate values.
All Derivations are updated synchronously by default. This means that, for example, actions can safely inspect a computed value directly after altering the state.
Pros
- A lot less mental overhead (no need to normalize state, no single ui tree, no reducers, no optimizing mapStateToProps, no connected components).
- Developer velocity and productivity. You can very quickly put together decent sized applications with Mobx and the code also scales well within the large teams.
- Porting services/view models from AngularJS application would be straight forward.
- Mobx with React is blazingly fast in scenarios cut out for it. During the updates test scenario (from my earlier post), Mobx refreshed price/volume changes with-in 2ms (with 1500 tickers on the page).
Few things to watch out for
- Mobx automatically runs all derivations (updating views, updating computed properties) whenever the state changes. It internally manages the dependency state graph. Its implicit nature of updates feels like magic. This may become problematic in large applications where you might prefer more control. (just my opinion, I may be wrong).
- Observable arrays inherit from Object instead of Array, so the external libraries (like lodash) won’t be able to detect Observable arrays correctly. You need to convert to regular Array by calling observable.toJS() or observable.slice().
- Mobx had higher memory footprint than Redux in some of my test scenarios. This may not be a big deal for most of the applications but please test out your specific scenarios.
- For promises, every callback handler needs to wrapped with @action and derivations will be triggered for every callback handler.
- Mobx synchronously triggers derivations whenever an observable property is changed, this can cause performance bottlenecks. Enforcing strict mode right from the beginning helps with this issue.
Helpful links:
https://medium.com/@mweststrate/becoming-fully-reactive-an-in-depth-explanation-of-mobservable-55995262a254
https://mobx.js.org/best/pitfalls.html
Thanks for reading.
P.S. Thanks to Shyam Arjarapu for reviewing this article.