Re-rendering is not always efficient. In the process of rethinking and modernizing our front-end stack here at 500px, I’ve spent the last month or so deep diving into React, Redux, and the dizzying array of tech surrounding them. As a relative newcomer to React and Redux (I know, I’m a bit late to the party), […]
In the process of rethinking and modernizing our front-end stack here at 500px, I’ve spent the last month or so deep diving into React, Redux, and the dizzying array of tech surrounding them. As a relative newcomer to React and Redux (I know, I’m a bit late to the party), I found it a bit challenging to find a good resource clearly laying out the details of how, and more importantly, when, components are rendered. Understanding and controlling rendering is essential to writing good, bug free and performant React + Redux code. It is the core of the framework. Let’s get started.
React renders a component whenever its props or state change. This is fairly easy to understand, but it’s actually a bit misleading; it is not the only cause of a render. Components are also rendered when their parent is rendered. In fact, the entire component tree below the updated component is blindly re-rendered, whether or not their own state or props changed at all.
It’s important to keep in mind that in React, rendering does not necessarily mean updating the DOM. There are two stages in React rendering:
Since re-rendering an unchanged component does not result in any real DOM updates, it’s not as costly as you might think, especially for simple components. For the most part, it’s not a problem and you can leave well enough alone. But what can you do when it is a problem?
Sometimes a large number of unnecessary render calls can start to weigh down your application. React provides a hook to take control of rendering in the form of the shouldComponentUpdate lifecycle method.
React checks this method to determine whether or not to render a component, and by default it simply returns true. If you implement shouldComponentUpdate yourself, however, you can use it to check whether or not props which affect the rendered component have changed and only render if necessary.
Be careful, though. In simple components, this process can actually be more time consuming than just rendering, especially when you start doing deep object comparisons. It also makes understanding and maintaining the component more difficult, and leaves you more vulnerable to introducing subtle bugs in the future. As with any performance optimization, you should only be doing it if you need to, and not a moment earlier.
In many cases, you may want to do a simple check to determine if any props have changed before deciding to render. Rather than writing a shouldComponentUpdate method to handle this for every component, you can extend React.PureComponent, which will take care of it for you. You can also use a helper function like pure() from Recompose, which has the same effect as extending PureComponent with the added benefit of working with functional components as well. Keep in mind, though, that both methods only perform a shallow comparison of your new and old props. That is, if you change an attribute of an existing object in your props or state, a pure component will not notice. It will see the same object referenced in both new and old props, and conclude that nothing has changed. With some care and good practices, though, this shouldn’t be an issue — but you need to be careful.
When you connect your component to a Redux store using the connect function, which maps Redux state to component props, Redux will ensure that the props are updated and a render is triggered whenever the relevant Redux state has changed. It does this with a shallow compare, though, so Redux reducers must always return new state objects instead of simply mutating the existing state. Essentially, Redux is taking care of converting your component into a pure component, without the need of manually implementing shouldComponentUpdate yourself.
Making sure you always return new state in your Redux reducers can be tricky at times. It can also be costly, involving expensive deep copying of state objects. As such, it can be helpful to use a library which enforces immutability, such as Immutable.js. This library provides immutable implementations of common collections, making updating Redux state generally both cleaner and less error prone. It also implements some fancy data structures behind the scenes which make updating state a lot more efficient than deep copying large objects or lists. It is not without its drawbacks, though, as you’ll now have to access properties through get() and set() accessor methods, and you’ll lose some nice syntax like destructuring.
As immutable objects start to spread themselves through your code, it also makes it very easy to create pure components, since your props can be reliably compared with a simple shallow equality check.
React rendering is smart, but not as smart as you think it is. If a component’s state changes and triggers a re-render, it will render all of its child components, all the way down the tree. Generally, this is fine; remember that premature optimization is the root of all evil (or thereabouts). Also remember that a render does not necessarily result in any change to the DOM, as the virtual DOM diffing will still take place. But should it become a problem, you can tame it by taking control of the decision to render with shouldComponentUpdate and PureComponent.
Redux is a bit smarter and helps ensure that connected components are only rendered when the state they care about changes, but you need to be careful when implementing your reducers to ensure they always return new values and don’t simply mutate objects, as Redux won’t notice the update. Immutable.js can help you out with this, and provide some other benefits, but again, be careful to not over-engineer your solution and add complexity and dependencies which you might not ever need.
Understanding how React and Redux decide to render components is key to writing and debugging your React projects. Knowing how to take more control over this process is equally important — just be sure to wield your new power responsibly!