How does React decide to re-render a component?

Published on: February 8, 2017

Tags: react, js, and performance

React is known for its performance. Because it has a virtual DOM and only updates the real DOM when required it can be much faster than updating the DOM all the time, even to display the same information. However, React’s “smarts” only go so far (at the moment!), and it’s our job to know its expectations and limitations so we don’t accidentally hurt performance.

One of the aspects we need to be aware of is how React decides when to re-render a component. Not as in “update the DOM render,” but just to call the render method to change the virtual DOM. We can help React out by telling it when it should and shouldn’t render. Let’s look at both of those in turn...

1. The component’s state changes

A re-render can only be triggered if a component’s state has changed. The state can change from a props change, or from a direct setState change. The component gets the updated state and React decides if it should re-render the component. Unfortunately, by default React is incredibly simplistic and basically re-renders everything all the time.

Component changed? Re-render. Parent changed? Re-render. Section of props that doesn't actually impact the view changed? Re-render.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Todo extends React.Component {

    componentDidMount() {
        setInterval(() => {
            this.setState(() => {
                console.log('setting state');
                return { unseen: "does not display" }
            });
        }, 1000);
    }

    render() {
        console.log('render called');
        return (<div>...</div>);
    }
}

In this (massively contrived) example the Todo will re-render every second, even though the render method doesn’t use unseen at all. In fact, unseen doesn’t even change its value! You can check out a working version of this on CodePen.

Well, but re-rendering all the time isn’t helpful...

I mean, I appreciate that React is being super careful. It would be worse if the state changed and the component didn’t render when it was supposed to. How would I know about that new message my friend sent me?! I’d miss it, so she’d probably assume it was intentional, then she’d stop talking to me, and the whole friendship would be ruined. All for the want of a little green dot not re-rendering. High stakes. Re-rendering is definitely the safe option.

But re-rendering seems expensive (and your example is melodramatic)

Yes, re-rendering unnecessarily does waste cycles and is generally not a good idea. However, React can’t “just know” when it’s safe to ignore parts of the state. So it plays it safe and re-renders whenever there’s a change to the state, important or not.

How can we tell React to skip re-rendering?

Well that brings us nicely to point two...

2. shouldComponentUpdate method

By default, shouldComponentUpdate returns true. That’s what causes the “update everything all the time” we saw above. However, you can overwrite shouldComponentUpdate to give it more “smarts” if you need the performance boost. Instead of letting React re-render all the time, you can tell React when you don’t want to trigger a re-render.

When React comes to render the component it will run shouldComponentUpdate and see if it returns true (the component should update, a.k.a. re-render) or false (React can skip the re-render this time). So you’ll need to overwrite shouldComponentUpdate to return true or false as needed to tell React when to re-render and when to skip.

When you use shouldComponentUpdate you’ll need to decide which bits of data actually matter for the re-render. Let’s go back to our example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Todo extends React.Component {

    componentDidMount() {
        setInterval(() => {
            this.setState(() => {
                console.log('setting state');
                return { unseen: "does not display" }
            });
        }, 1000);
    }

    shouldComponentUpdate(nextProps) {
        const differentTitle = this.props.title !== nextProps.title;
        const differentDone = this.props.done !== nextProps.done
        return differentTitle || differentDone;
    }

    render() {
        console.log('render called');
        return (<div>...</div>);
    }
}

As you can see, we only want to re-render the Todo if the title or done attributes have changed. We don’t care if unseen has changed, so we don’t include it in shouldComponentUpdate.

When React comes to render a Todo component (as triggered by the setState) it will first check if the state has changed (via the props or state). Assuming the state is different (which it will be because we made an explicit setState call) React will check the shouldComponentUpdate on the Todo component. React will evaluate if shouldComponentUpdate is true or false, and decide to render from there.

With this updated code the setState will still be called every second, but the render will only happen on the initial load (or when the title or done properties change). You can see this happening here.

Seems like a lot of work to define all that...

It can be. This example is especially verbose because there are two properties we care about (title and done) and only one we are happy to ignore (unseen). Depending on your data it might make more sense to check for just one or two properties and ignore a whole bunch.

Important note

Returning false does not prevent child components from re-rendering when their state changes.

– Facebook's React docs.

This applies to the children’s state but not their props. So if a child component is internally managing some aspect of its state (with a setState of its own), that will still be updated. But if the parent component returns false from shouldComponentUpdate it will not pass the updated props along to its children, and so the children will not re-render, even if their props had updated.

Bonus: simple performance testing

Writing and running computations in shouldComponentUpdate can be expensive so you should to make sure they’re worth the time. Before writing any shouldComponentUpdates you can check how many wasted cycles React does by default. With this information to guide you, you can make informed decisions about which components are re-rendering too often and causing performance problems.

Use React’s Performance Tools to find wasted cycles:

1
2
3
4
Perf.start()
// Do the render
Perf.stop()
Perf.printWasted()

Which components wasted a lot of render cycles? How can you make them smarter with shouldComponentUpdate? Try some ways and be sure to check them against each other with the performance tools!

Resources

Many thanks to my co-worker Marcin for explaining how React makes these decisions.


comments powered by Disqus