Efficient Nested React Components

Joshua Comeau
6 min readMar 21, 2015

March 2017 update: Yo reader! This article is 2 years old, using React 0.12, in a pre-redux world. I suggest you check out more modern examples of React perf tuning.

This is really just here for historical purposes, but shouldn’t be taken literally for modern-day perf stuff.

The official docs are a great place to start!

If you ask people what React’s greatest selling point is, you’ll likely hear performance. Because of its fancy virtual DOM, it does far less page manipulation than, say, Angular. React is well-suited to apps with lots of dynamic data because of how quick it is.

Just because React can be fast doesn’t mean it will be, though. As a fun side project, I’m making a word game (source code) similar to Word Dojo, using React and Flux. It involves a grid of letters, like this:

In terms of React components, I have a GameBoard as my parent component, which holds 10 TileColumn components, which in turn each hold 10 Tile components, for 100 total tiles.

Tiles have two properties in their state: their letter (string) and their status (boolean). In the example above, the orange letters have been clicked and are ‘active’.

The overall game state is held in a GameStore, and clicking a tile uses an ActionCreator to pass the click information to the GameStore, which deals with all the logic and updates its model. It then emits a change event.

What the Flux? If you’re a React fan but haven’t played with Flux, that above paragraph might be meaningless to you. Don’t worry about it, though; this is primarily a React article, and the salient points should translate.

Which component should listen for that change?

My initial thought was to make each tile in charge of its own state: They would each register with the GameStore to be notified of changes, and if the change affects them, they’d update. This led to 100 change listeners being registered, and React gave me a warning. It didn’t like that.

The alternative is to register with the parent: the GameBoard. This top-level component would listen to the GameStore, and when it received a broadcast, it would pass the new state info down to all of its children. Those children would get re-rendered if their state changed.

That’s how it should work, at any rate. I put a quick console.log into the Tile component’s render function to see what was happening:

Every time I click a letter, the entire board gets re-rendered, one tile at a time! I fired up the profiler to take a look at how long this was taking:

There’s a lot of numbers here, but the one we care about is Total Time. 41.1ms.

That seems pretty quick, but the game is as simple as possible right now, and it’s only a 10x10 grid; I might want to make the grid bigger.

Performance matters. In an awesome set of slides by Google engineer Addi Osmani, he explains that 100ms is the longest possible time a user interaction should take to complete. Anything beyond that doesn’t feel responsive. For a game, that threshold is presumably much lower, so 40ms is unacceptable.

Selective Rendering

While React is smart enough to avoid mutating the DOM when it doesn’t have to, it still runs its render() function to generate a new DOM representation. When there are a lot of components, it still has to do a fair bit of work, even when nothing’s changed.

React errs on the side of caution.

The Tile component’s render function I’ve written is “pure”. This means that it will render the exact same thing, every time, given the same state and props. There are no other variables at play: state and props are the only two things that affect the component.

Not all components are like this, though, and React doesn’t want to assume.

Thankfully, React lets us specify a custom function to dictate whether the component should be re-rendered or not, called shouldComponentUpdate. Throw in whatever logic you want; return true if you want it to re-render, false if it should skip it this time.

We could write something like this, to force React to not update when the props are the same:

shouldComponentUpdate: function(nextProps) {
return nextProps.tile.active !== this.props.tile.active;
}

When the game board renders, this.props.tile.active will be false. When we click on a tile, nextProps.tile.active ought to be true, and because they aren’t equivalent, this function returns true. If, like 99 of the tiles after a click, the two values are the same, it should sit on its hands and do nothing.

Sadly, this did not work, though. The virtual DOM was still being computed 100 times a click.

Variables are pointers.

Consider this:

var Jim = { fingers: 10 };    // Jim has 10 fingers.
var Jon = Jim; // Jon also has 10 fingers.
Jon.fingers--; // Oh no! Jon lost a finger.console.log(Jon.fingers); // Outputs '9', as expected.
console.log(Jim.fingers); // Also outputs '9'. But Jim didn't
// lose a finger..?

Variables in Javascript point to an object in memory. When that object changes, all of the variables that point to it reflect that change.

How does this relate? Well, let’s look again at shouldComponentUpdate:

shouldComponentUpdate: function(nextProps) {
return nextProps.tile.active !== this.props.tile.active;
}

this.props.tile is a variable pointing at an object being stored in our GameStore. When the GameStore updates its active variable, it also updates the current props. That means that nextProps.tile and this.props.tile are pointing at the exact same object, so they will always be equal.

The Solution

The first problem can be solved with that custom shouldComponentUpdate function (there is also a mixin that does the exact same thing in even less typing!).

The second problem can be solved by creating a fresh javascript object on every state change. Here’s how a tile used to be made active, in the GameStore:

_board[column][row].active = true;

And here it is now, with a fresh object:

_board[column][row] = {
letter: _board[column][row].letter,
active: true
};

Even Better: ImmutableJS
This solution works, but it’s not very elegant; every time I add a new property to the tile, I’d have to repeat that ‘val: obj.val’ nonsense. The better way, which I intend to move to, is to use a tool like ImmutableJS.

With those two changes, React only renders the tiles that actually need rendering.

Performance Gains

Running the Profiler again, we see a different story:

Total time went from a sluggish 41.1ms to a blistering 3.0ms. More than a 10x increase in speed. That’s huge.

For Fun: The Big Salad

I decided to up the ante a little bit, and make it a 100x100 grid instead of a 10x10 grid. With 10,000 tiles, the delay is even more pronounced:

Before
After

Yes, those numbers are real. Without the optimizations, a somewhat-acceptable 95ms turns into an ungodly 4 seconds.

Straight from the horse’s mouth

Wanna play around with it for yourself? View the source code on GitHub.

Credit where credit is due

Most of the information here was given to me by super helpful StackOverflow answerer Kakigoori. I’m really just rehashing what he said, and adding pictures. He also has a terrific JSBin that shows how wasteful React can be without these techniques.

--

--