Making React reactive: the pursuit of high performing, easily maintainable React apps

on June 22, 2015

Share:

Edit 2-3-2016: Mobservable has been rebranded to MobX

How do you build blazing fast react apps? Recently, we started using React in one of our large scale projects and it has helped us big time thanks to its structured way of building components and its fast virtual DOM that saves tons of UI updates. The beauty of this project is that it has quite some nice challenges; it needs to draw thousands of objects in the browser and these objects are highly coupled to each other. Values of one object might be used in an arbitrarily amount of other objects so small changes might require updates in many unrelated parts of the UI. These values might be updated by drag and drop actions of the user, so to keep the UI responsive, all updates and repaints have to happen in less than 40 milliseconds. And although plain React is fast, it didn’t take us too long to figure out that React alone wouldn’t do the job.

So we started searching for a solution that would offer us the needed performance and still allow our code base to be maintainable according to React principles. In short, we wanted an elegant solution. So we tried to leverage a concept from the world of Functional Reactive Programming; namely Observables. The selling point of observables is all computations automatically detect which other observables they use. The computation will then be re-evaluated automatically when one of these observables changes in the future. Observables are a concept used in other UI frameworks, such as Ember and Knockout. We figured out that if all our model objects became Observables and all our React components became Observers of the model, we wouldn’t need to apply any further magic to make sure the relevant part, and only the relevant part, of our UI gets updated. Read on to see all the awesomeness revealed. There are even numbers near the end!

Let’s start with a contrived example to make this a lot less theoretical (or less fussy if you prefer). Imagine a react app that represents a small shop. There are some articles, and there is a shopping cart in which you can put some of these articles. Something like this:

Shopping cart demo

Poof, there it is in real life.

The data model

For starters, lets define the data model. There are articles with a name and price and there is a shopping cart with a total cost, which is based on the sum of its entries. Each entry refers to an article, stores an amount and has a derived price. The relations within our data model are visualized below. Open bullets represent the derived data that should be updated if some other data changes and so should its representation in the UI. So even in this simple model a lot of data is flowing around, and a lot of UI updates are required when stuff changes.

demo shop data model

Lets compile a list of requirements:

  • If the price of an article changes, all related shopping cart entries should re-evaluate their price.
  • .. and so should the total costs of the cart.
  • If the amount of articles in the cart changes, the total costs should be updated.
  • If an article is renamed then its view should be updated
  • If an article is renamed then the view of related cart entries should be updated
  • If a new article is added tot the cart..
  • etc.. etc..

Probably by now the gist of our UI problems is clear. As a programmer, you don’t want to write boiler-plate code to process all kinds of possible updates, but your user might have to wait inconveniently long if your application is always re-rendered upon each data change.

So, lets solve this problem once and for all and write down our data model:

Ok, that wasn’t too hard, right? The above constructor functions rely heavily on the MobX library, which provides a stand-alone implementation of the observable concept (it should combine with other javascript based libraries just as easy as with React). The props function creates new, observable properties on the target object, based on the provided keys and the types of the values. Due to the observable nature of all properties, the above functions are automatically (and only) updated when some of its dependencies change. This immediately fulfills some of our requirements, as, for example, the total of the cart is automatically updated when new entries are added, when the price of articles changes, etc.

The user interface

Seeing is believing, so let’s build an user interface around this model. We create some React components that render our initial data. The following JSX snippet shows a view on the shopping cart, renders all entries in the cart and shows the total price of the cart. As you can imagine, other components in the app, like the view on articles, are very similar.

Pretty straight forward, right? A CartView component receives a cart, renders its total and its individual items using the CartEntryView, which in turn prints the name of the related article and the amount of articles desired. According to React best practices, each listed item should be uniquely identifiable, so we assign an arbitrary yet immutable ID to each entry. The remove button drops this amount by one and if it hits zero the entire entry is removed from the cart. Note that nowhere in the removeArticle function did we indicate that the UI should be updated.

The next big step is forcing those components to stay up to date with the data model, for example when the entry is removed. As you can determine easily from the rendering code, there are a lot of possible data transitions; the amount of articles can change, the total cart costs can change, the name of an article can change, even the reference between entry and article can change. How are we going to listen to all these changes?

Well, that is pretty straight forward. Just use mobxReact.observer from the mobx-react package to each component and that is enough to settle all our other requirements:

Wait, what, that’s all? Yup, just check out the demo and the source of the above at JSfiddle. So what happened here? The observer function did two things for us. First, it turned the render function of the component into an observable function. Secondly, the component itself was registered as observer of that function, so that each time the rendering becomes stale a re-rendering is forced. So this function (and decorator if using ES6) makes sure that whenever observable data is changed, only the relevant parts of the UI are updated. Just play around in the example app, and while doing that, keep an eye on the log panel and see how the UI is updated based on your actual actions and the actual data:

  • try to rename an article that is not in the shopping cart
  • add an article to the cart, then rename it
  • add an article to the cart, and update its price
  • remove it from the cart, update its price again
  • … etc. You will notice that with each action, the minimum amount of components will be re-rendered.

Finally, since each component tracks its own dependencies, there is usually no need to explicitly re-render the children of a component. For example, if the total of the shopping cart is re-rendered, there is no need to re-render the entries as well. React’s own PureRenderMixin makes sure that that doesn’t happen.

The numbers

So what did we achieve? For comparison purposes, here you can find the exact same app but without observables and a naive ‘re-render-all-the-things’ approach. With only a few articles you won’t notice any difference, but once the number of articles is ramped up, the difference in performance becomes really significant.

Performance of creating and rendering 10.000 articles and cart entries

Updating 10 articles

Creating large amounts of data and components behaves very similar with and without observables. But once data is changed, the observables really start to shine. Updating 10 articles in a collection of 10,000 items is approximately ten times faster! 2.5 seconds dropped to 250 milliseconds. That is the difference between a lagging and a non-lagging experience. Where does this difference come from? Let’s first take a look at the React render reports after running the updates in the “update 10 articles in a list of 10000 articles” scenario without observables:

Rendering report after naive full app rerender

As you can see, all twenty thousand ArticleViews and CartEntryViews are re-rendered. However, according to react 2,145 of the total of 2,433 milliseconds of rendering time were wasted. Wasted means: time that was spent on executing render functions that did not actually result in an update of the real DOM. This strongly suggests that naively re-rendering everything is a big waste of CPU time if there are many components. For comparison, here is the report of the same scenario when observables are used:

Render report after observable driven re-rendering

That is a big difference! Instead of re-rendering 20,006 components, only 31 components are re-rendered. And more importantly, there is no waste reported! That means that each and every re-rendered component actually changed something in the DOM. That is exactly what we intended to achieve by using observables!

From the report, it becomes clear that most of the remaining rendering time, 243 of the total 267 milliseconds, is spend in rendering the CartView, which is re-rendered just to refresh the total costs of the cart. However re-rendering the CartView also implies that all ten thousand entries are revisited to see if any of the arguments to the CartEntryViews did change. So by simple putting the total of the CartView in its own component, CartTotalView, the whole rendering of the CartView can be skipped if just the total costs changes. This drops our rendering time even further to roughly 60 milliseconds (see the ‘optimized’ series in the chart above). Thats roughly 40 times faster than the same update in our vanilla React app!

Conclusion

By using observables we built an application that is an order of magnitude faster than the same app that naively re-renders all components. And, as important (for you as a programmer), we did this without compromising the maintainability of the code. Just take a look at the source code of the two JSFiddles linked above; the two listings are very similar and are both equally convenient to work with.

Could we have achieved the same with other techniques? Maybe. For instance, there is ImmutableJS, which also makes the React rendering very fast by only updating components that receive changed data. However, you have to do much larger concession on your data model. After all, IMHO, mutable classes are in the end a tad more convenient to work with than their immutable counterparts. Besides, the immutable data structures do not help you to keep your calculated values up to date. So with immutable data, changing the name of an article would really fast re-render the ArticleView, but still not invalidate any existing CartEntryViews that refers to the same article.

Another technique one can apply to optimize a React app is to create events for each possible mutation in the data and (un)register listeners for those events at the proper moments in time and in the proper components. But this results in tons of boiler-plate code which is error-prone to maintain. Besides, I think I’m just too lazy to do such things.

By the way, I strongly advise using controllers or action dispatchers as an abstraction around updating your model data to keep the separation of concerns clear in your project.

To conclude, in large projects combining React with Observables worked so well that sometimes I saw data mutations correctly updating the UI in corner cases I did not even think of yet, without hitting any performance issues. So for me, I’ll leave the hard work of figuring out when and how to update the UI as fast as possible to React and Observables, and focus on the interesting parts of coding :).

Discuss this post on Hacker News.

Resources

Subscribe to Our Blog

Receive Mendix platform tips, tricks, and other resources straight to your inbox every two weeks.

RSS Feed of the Mendix Blog

About Michel Weststrate

Michel is a 30 years old software developer graduated at the Delft University of Technology and currently working as lead developer at Mendix / Sprintr. Michel is happily married to Elise and father of two daughters. In his rare free time, you can find him reading or writing even more software.

  • I’m still wrapping my head around React, but doesn’t this kind of defeat the purpose?

    I thought the notion was you didn’t have to worry about granular model changes to avoid massive UI updates – just re-render everything because React uses a virtual DOM to enact only the changes it needs on the real DOM. But, if you’re already observing individual model changes, you could just skip React entirely and make conservative updates to real DOM based on knowing exactly what changed.

  • Hi Les,

    Very valid question. In the most ideal scenario you would always re-render a complete app indeed, but this doesn’t really scale well beyond small apps. Rendering apps partially is already common practice in React land. Flux also favors this pattern, see for example https://facebook.github.io/flux/docs/todo-list.html#listening-to-changes-with-a-controller-view, where subscriptions to stores (data collections) are made in specific components, so that they know when to update.

    As you can see managing those subscriptions are both tedious and error prone (it is easy to forget edge cases where the ui should be updated). Partial updates however do not defeat the purpose of the virtual dom; since applying a virtual DOM diff for these partial updates is still way faster compared to restructuring a fresh new DOM for that specific part of the UI. Also React / the virtual dom helps you in writing elegant components, that properly clean up etc etc, which is a lot harder to achieve in handcrafted code, where arbitrary parts of could could change arbitrary parts of the dom.

    You could indeed calculate and apply real DOM updates manually using observables (that is exactly what I did in the Jquery fiddle https://jsfiddle.net/mweststrate/vxn7qgdw/), but using React just saves you doing this all manually without losing performance. If you view the React fiddle of the same demo (https://jsfiddle.net/mweststrate/46vL0phw/), you’ll notice it is way more elegant than the jquery version.

    I hope that clarifies the idea a bit.

  • Tixelated

    Hi @michel_weststrate:disqus ,

    Could you take a look at the JSFiddle demos? It appears that they all are either broken or missing.

  • Apologies! They should be fixed now.

  • alexsdisqusaccount

    Completely agree. There are situations where react’s pure render just isn’t granular enough. If you’re doing a lot of data manipulation, you _don’t_ want to redo all of those heavy calculations on every render, only when it _needs_ to happen. You choices here are to split it out into its own component, cache the value to the component and handle the updates manually (eg by hooking componentWillReceiveProps), or by using observables.

    Using this pattern I’ve been getting great performance _and_ code that’s easier to reason about in a very complex UI thing that’s in production at work.

    The only question I have is why didn’t you use RxJS? I know that seems like a bullshit comment but it’s not like Rx is grunt and you invented gulp; Rx is the defacto standard for observables in javascript. What does MOBservable do better?

  • Hi Alex,

    Good question, I will try to write a blog post about that in the next few weeks, but the gist about it is this: In RxJS you need to explicitly observe and unsubscribe, and you have to think about all your data as being streams and you need to use some stream combinator to apply calculations. MOBservable hides most of the subscription logic for you by relying on the GC itself. Also, you don’t observe streams, but expressions and the dependency tree is constructed for you in the background.

    So instead of writing

    budget = creditCardLimitStream.combine(reservedMoneyStream, (ccl, rm) => ccl — rm);

    you write

    budget = mobservable(function() { return creditCardLimitObservable() – reservedMoneyObservable(); });

  • alexsdisqusaccount

    I noticed that after looking into it more, that’s pretty clever! Actually, the whole thing is really cool. Especially interested in checking out how you’re achieving that “atomic” characteristic in your readme; I currently use a single immutable store for core business logic + Rx for dependent data, and multiple runs of composeLatest is the most annoying thing.

  • hgeldenhuys

    Hi Michel, I was wondering if you ever looked at Mithril as an alternative to React. I understand that React is the more popular one, but the MVC philosophy, larger scope, faster renderability and smaller footprint makes it very appealing. Just fishing to hear what your opinion is?

  • Hi Herman,

    The ideas look very good, I see in the doc a lot of same ideas and separations of concerns similar as those in React. In would need to work with it to say anything more in depth then that about it 🙂

  • Interestingly, this type of “unobtrusive reactivity” is exactly what http://vuejs.org/ was created for. Vue actually makes POJOs reactive by converting the properties into ES5 getter/setters. Personally I think React’s core value is the functional aspect of it; if you really want mutable state Vue is even simpler than Mobservable 😉

  • Tarun Ramakrishna

    Not sure how this fits into the react js best practice that “props” should not be changed internally and “state” should be used instead

  • Hi Tarun,

    Mobservable doesn’t alter state or properties of a component. It just subscribes to data, the same way as components subscribe to stores in flux, with the single difference that mobservable detects itself which subscriptions need to be made, so you don’t have to declare or maintain those yourself.

  • Hi Evan,

    How reactive data structures work is very similar to vuejs indeed; objects with ES5 getters / setters to be able to track dependencies. The difference is that mobservable is a standalone lib, just for working with reactive data and functions, while most other implementations are build around a view layer (vuejs, knockout, meteor, ember, etc) and as such can only be used when adopting a larger framework.

    Since vuejs controls both view and data, you don’t have to make data explicitly reactive indeed. But I think passing data just once trough `makeReactive` is a small price to pay for a solution that can be applied in any environment. (since 0.6 makeReactive will also make any data that is assigned in the future to the same object tree reactive).

    Ah notable difference between the implementation of computed functions is that Mobservable executes them synchronously, which should make it easier to reason about and make them more generally applicable.

  • Yep, I think coupling the reactive system with the view layer has its benefits, but decoupling them sure also has its use cases. Re: synchronous updates – you can disable async mode in Vue, but async mode offers some nice properties because it gives Vue the opportunity to dedupe and optimize execution order for the updates (similar to Ember run loop, but automatic).

  • Re: synchronous updates. Yeah the mobservable implementation yields the same behavior even when computing changes synchronous. For single statements it makes sure updates are atomic, even if a change influences computations via different dependency paths, multiple statements can be wrapped in a synchronous transaction if needed (in practice I never needed it so far) to postpone updates until all data is settled. But in the end mobservable + react is quite similar to VueJS it think.

  • Tarun Ramakrishna

    Yep, got this after a re-read thanks!

  • Joe Privett

    Meteor does reactive updates pretty well.

  • Depends; meteor is nice in the sense of full stack reactivity, but the implementation itself is a lot more inefficient (unecessary recomputations) and flaky, see for example: https://forums.meteor.com/t/tracker-2-0-the-evolution-of-tracker-solutions-to-its-core-problems/. Some people are even investigating whether Meteor Tracker couldn’t be replaced by Mobservable for it’s better runtime characterstics.

  • Vyacheslav Sholohov

    Looks awesome! I’ve got a question, though – can we do something with blocking UI thread while 10000 elements being rendered?

  • Sebastian Markbåge

    It is interesting that you include initial rendering in your benchmark. However, it seems counter-intuitive that adding observables would make initial rendering FASTER as your example suggests. Since you have the raw data + the observables to manage where as React only has the raw data to manage. Could you elaborate a bit on this? When I rerun your JSFiddles, React seems to be faster for the initial rendering as I would expect.

    Also, you’re benchmarking using the development mode build of React which is much slower and have different performance characteristics than the production build. This is due to extra bookkeeping and checks that help issue friendly development time warnings. Always benchmark with a production build of React.

  • Hi Sebastian,

    Thanks for the tip on the production build benchmarking. Should have done that indeed! I guess I just assumed that printing the React render performance reports wouldn’t work in a production build.

    Although the overhead of making the objects observable is in usually neglectable, it can indeed not be faster. Note that the 1000 items setup with observables is slightly slower and the 10.000 items setup slightly faster than the vanilla approach. I think this is primarily the result of my test setup (probably I had other browser windows or apps open at that time). I think that the important conclusion here is that the initial render is not significantly slower, while re-rendering might be orders of magnitude faster.

  • Nice thinking. As far as I know React can only render on the main thread. This might be different for other vDOM implementations though.

  • Hi Michel, nice article. I worked with React for quite some time now and I have a problem with one idea you mention. Building observables for a react component brings it to “close” to a model and to “far” away from being really maintainable, especially when you build it large scale. The reason being, many large scale apps make models depend on each other. Some of the data from those models needs to be processed model by model.. This won’t work with an architecture that is too “close” to a model. You mention controllers and I call them data containers. Those containers should do the work to direct data to where it belongs.

    Second, this is not a React thing, you mention to put as many events as possible (as possible in terms of necessary mutations) to your application. I recently published an article about Event-Driven Architecture. One of the goals with ED-Architecture is that you try to be very cautious with events. The more events you place the less maintainable it becomes. Reason being, every event triggers one or more routines and those routines are hard to manage/predict down the road.

    Any way, nice post.

  • Vyacheslav Sholohov

    I’m not talking about separate flow, though it would be awesome, i’m talking about implementing in react simple JS pattern of non-blocking rendering. For example, if i need to render 10000 objects, usually i’m slicing like 100 items, render those, set timeout for 0, then slice 100 more until there’s nothing left. I was just wondering, is there pattern to do this in react? Best practices sort of thing.

  • Hi Michel,

    Actually Mobservable was designed with the opposite in mind. In most flux implementations, especially Redux, it is more important to have your data model in sync with the component tree as in Mobservable. In Mobservable components you can pick your data from any where (props, state, instance fields, global variables, closuers..). It really doesn’t matter, as long as the data is observable the components will respond. So this makes your data model way more flexible and less tightly coupled to the components. A nice example of this is that it is no problem to have cyclic graphs in your data model (object referring directly to each other), while this is a problem for (to mention one) Redux where you have to normalize your data first.

    When diving into Mobservable, you will find that Mobservable is more predictable in its updates then Meteor, knockout or angular. Mobservable allows for working with events, but you have to mainly think in terms of derivations. The derivation tree is automatically ordered and evaluated synchronously in Mobservable so this makes it more declaratively and less ‘spaghetti’ compared to event based systems where you as a developer are responsible yourself for registering and ordering events.

    Btw, do you have a link your the article ;-)?

  • Afaik there is no standard approach to these kind of problems, but you might find this an interesting related thread: http://stackoverflow.com/questions/30976722/react-performance-rendering-big-list-with-purerendermixin/31375087#31375087

  • Jupp, here the one to EDA: https://www.linkedin.com/pulse/event-driven-architecture-michel-herszak and two others about React Flux, Relay and GraphQL are there, too.

  • mikekidder

    Hey Michel, good stuff. Fiddle needs an update on line80 with the name changes for mobservable -> mobx: inside the generate function.

    80 mobservable.transaction(function() {

  • Fixed, thanks!

  • Greg P

    I’ve been tinkering with Rx frameworks for a bit now and used RxSwift for a production app. I think for me the most exciting part about MobX is that I might actually be able to convince people to use it. Reactive streams are a huge leap from imperative programming and with something like this they can still do imperative and get the benefits of reactive thinking. Also it takes out the hardest parts of Rx which is usually ‘Hmm which operator/method should I use for this… This one seems to do the same thing over here… Or I can combine these two…”. I like that you can just manipulate the data however you see fit and get the results. Hopefully the React library will help it gain traction.

  • You shouldn’t render 10,000 elements at once – you can’t expect a UI to be responsive when it’s inherently single-threaded. You could make clever use of web workers – but I’m sure this would require some work to sort out. Or you can just render 500~ elements at once and paginate the other 9,500 – why would any UI have to display 10,000 items at once? Even if you could display 10,000 items at once most likely the user would have to scroll… so again why scroll just paginate.

  • Sure you shouldn’t render 10000 todo items at once. But having a few thousands items in a visualization is pretty standard for us. And then you need these performance numbers for mobile devices or drag and drop to be not laggy. What this benchmark shows is that performance degrades linear with the amount of components that are rendered, and that we can do better. Whether your applications does need that linear performance depends on your app. Our does 🙂

  • There’s more than one way to skin a cat ;). If your business requests visualizations that need 10,000 items then you can go back and challenge their request with an alternative solution, filtering the data and hiding the other 9,500 data points. The idea here is that a human can’t process that much information at once regardless of what the business wants – give them a excel sheet of 10,000 data points and ask them to digest all of it at once, its not feasible.

    Show me an example on mobile where you can see 10,000 items or data points at once… The screen is 5 inches – It just doesn’t fit.

  • Alex

    This is great, thanks for beating the drum on this and continuing to post good articles and speak about it at conferences. It took a long time for me to realize that this is a great approach and it took someone posting on medium about React Amsterdam to get me to come back. I kept hearing about it and looking and being a bit confused (probably when I saw that an @observable wasn’t the same as an rx.Observable) and running away but now I’ve gone through all of your medium posts as well as this and it’s more clear now.

    At first I dismissed it as just being another implementation of Object.observe with similar problems, but after wiring up a simple app I can see now how much simpler it is to mentally model.

    The amazing thing is it looks very close to the simplicity of angular 1’s use of plain old javascript objects (pojos) without having to add all the watchers and worry about dirty checks and re-rendering, a bit like what ember was trying to do with their models and computed properties, but with such a beautiful simplicity of just using decorators(also thanks to some of the work from Yehuda Katz from ember). Add to that not having to specify which values are actually being depended for computed on is almost black magic.

    It’s really great work you’ve done to reduce so much boilerplate to just a set of decorators. Easy to remember and use — an elegant design coupled with a great technical work to get it all going. Thanks again for sharing!

  • Thanks, good to hear!

  • hipertracker

    With immutable central app state tree you can use time travel debugging or restore/save any complete state of the application.

  • hipertracker

    Yes, try Inferno, the fastest Virtual DOM at the moment. https://github.com/trueadm/inferno

  • @michel_weststrate:disqus Which application you used for graphical data modeling?

  • Kamaraju

    Hi Michel ,

    I completely agree with les , i was still in a impression that Mobx under the hood still has virtual DOM , but reading your reply found that its not . Are we defeating the purpose of React ? I dont these Fiddle links working .