React + Redux Performance and the Benchmarks to Prove It

Steve Armstrong
Universal Language
Published in
6 min readDec 6, 2017

--

React/Redux is a wonderful pairing. Yet every decision you make can have major impacts on performance. Both offer performance improvements out of the box. React optimizes DOM updates. Redux optimizes updates to your state tree. Given these optimizations, developers assume that performance is a given. Well, it’s not.

This article assume the following knowledge:

This article only scratches the surface of what optimizations you can make. There are other articles that provide tooling and approaches to finding performance issues with React.

What I am mostly curious about is when I “optimize performance” in React/Redux, what difference is every optimizations making?

I could use whyDidYouUpdate to see that I eliminated unnecessary updates. I could use React dev tools to observe updates. I could have even used Chrome flame charts for visual feedback. In the end, I needed to know how much did my efforts impact performance. A 2x’s improvements, 3x’s improvement?

That’s why I turned to benchmark.js to help. Even better, there is a Karma adapter for benchmark.js that prints the results in the command line. I can run these headless, without needing to refresh the browser.

One other advantage of benchmarks vs profiling tools is that we can see how things perform on a smaller scale. It’s difficult to notice improvements when they are a tiny blip on a timeline. Numbers tell a much more straightforward story.

For instance, on my small list application below, the ops per second is negligible because they are so fast. However, imagine that this turns into a complex dashboard, with hundreds of list items and children. We can anticipate problems that might impact performance before we scale up.

The Setup

To test the approach, I set up a very basic application. Think of it more like an exercise than an application.

You can play along with the source code here:
https://github.com/sarmstrong/react-redux-benchmarks

The “exercise” is a basic list with child lists. I choose this structure because it creates a tree. The data is a tree, and the views are in a tree like structure.

<ul>
<li>Item One
<ul>
<li>Item One - Sub Item One</li>
<li>Item One - Sub Item Two</li>
<li>Item One - Sub Item Three</li>
</ul>
</li>
<!-- etc . . . . -->
</ul>

Tree like structures are one of the basic problems that React sets out to solve. We can easily pass down props from parent components to child components.

Redux compliments this idea even more. It creates a state tree that we use to feed our hierarchal view structures.

With performance in mind, I wanted to understand the impact that React/Redux has on our data. So I tested the following:

  1. How much can our React view impact our application
  2. How much can our Redux data structure impact our application

How much can our React view impact our application?

I created a simple list structure. I started with an imperative style, with each list item embedded in the parent. I did this to demonstrate state being passed down through the props.

Using this structure, I wanted to test the impact of how we created our React views. There are three ways:

  1. Functional syntax

2. Standard React Component

3. React Pure Component

In this structure, we have “connected” component as the top level parent component. Each child component is either functional, standard or pure.

How much can our Redux data impact our application?

Next I decided to see what difference data shape has on the application. This is one of the first recommendations in the Redux docs when introducing reducers.

In these series of experiments, I “flattened” the object structure. The children are no longer nested in the parent objects. I defined the top level parent items as a simple collection of ids.

Next, I wanted to test a few conditions:

  1. What happened when no data is changed
  2. What happens when data is changed

I choose this setup to illustrate a point, to show how our Redux/React app responds to changes.

Drumroll Please . . . . .

So what are the results you ask? Here is a print out of the benchmarks at the time of this article. (Using React 15)

Results of running benchmarks

When using a suboptimal data structure, pure components out-perform functional and standard components. However, when we optimize the data structure, view structure doesn’t matter.

With no updates, an optimized structure can lead to a drastic performance increase. Even with updates, there is still a major performance gain. It’s enough of a gain to justify any performance improvement. (Who wouldn’t want their app to run 2–3 times faster?)

So how does this all make sense????

When we pass the props down through the parent component to the children, we need to mutate the top level object. This means that every component has the potential to update.

When we use functional components with the nested structure, every view updates. The cost of these updates is expensive. Even more interesting, there is a negligible difference between when the content changes or remains the same. Reconciliation should reduce the impact on the DOM, but the React updates have a cost of their own.

Captured with React Dev Tools

When we switch to using pure components, we gain some control over updates. We still need to mutate the top level state, but the second and third list item references are not mutated. Shallow equals keeps the component and its respective child components from rendering. This is why we see a performance boost.

Captured with React Dev Tools

When we optimize the structure and there is no content change, React never updates. This is where Redux kicks in. Connected components by default are pure. When the properties are the same, the update never reaches the connected component.

When we use the optimized structure and there is a content change, only the child item is updated. State does not have to pass down through the parents to the child components. In this instance, using a pure component is slightly irrelevant. The connected component is handling the shallow comparison upstream.

Captured with React Dev Tools

Using this information, I can discern that data shape has the biggest impact on React/Redux performance. It seems counterintuitive but using “more” connected components is best for performance. Of course, you need to optimize your data structure first.

The benchmarks tell me what optimization will have the highest impact. This is information I can share with our Product team when choosing which performance tasks to tackle. We can weigh both the impact and the effort to determine their business value.

So there you have it, how to use benchmarks to tell a performance story. It’s not a novel idea, but it’s also something that we don’t naturally gravitate towards on the Front End. We spend a lot of time analyzing page load or TTI, but I’d venture that most of us don’t spend a lot of time benchmarking our source code.

It will also spur a lot of questions that you may not have the answer to, or reveal holes in your approach. For instance, when using functional components, why do updates have similar performance with stale content?

One other benefit of benchmarks that is not mentioned here, is the ability to see upstream impact. An update to a subcomponent’s benchmark could have an impact on a parent component. It there is no impact, it might steer you to evaluate other components instead.

I’ll leave you by stressing that performance should be one of the most important parts of delivering your product. We spend a lot of time on static typing, linting, unit testing and integration testing. This feedback only tells us “does it work”. Benchmarks can tell us “how well it works”. In the end, your users need both.

--

--