Practical React: Control Render Thrashing & Rendering Performance

Real Problems and How to solve them, in five minutes or less.

Problem

You’re all wired up to redux, and decide to add some UI flourishes or animation, but your rendering performance is in the toilet. Everything is stuttering and you don’t know where to turn.

Caveats:

  • NOT Redux Specific, this will occur as your app grows, and your component tree becomes more complex, causing many components to update, unnecessarily.
  • It’s important to be aware that performance tuning is a never ending endeavour, and will be highly specific to your app, these are some starter tips specific to react that’ll help get over some common issues.
  • Chasing 60fps UI performance is a serious undertaking, while this will help to get you on your way, you’ll want to look into “perceived performance”, and controlling the timing of rendering, data fetching and processing, where putting these events in series over trying to do them in parallel will begin to get you even closer. Combine this with offloading what you can to web workers, and you’ll be smiling all the way.
  • There are no panacea’s in react or life, so choose wisely. Put another way, if you already have a hammer, be cautious not to assume every problem is just another nail.

Solution: Erm, at least a start.

This is one of the first problems I encountered when I started with react. As soon as my app got to a non trivial size, (note it was still truly an single page without routing), I found my rendering performance was in the toilet, so I got stuck in, and these are the first things that worked for me.

Step 1: Control When Components Get Updated.

Part of the reason why react, and many other UI frameworks are fantastic to work with is that the expose some sort of binding between data and you components. Update the data, and voila! Your UI changes to reflect it, usually without very much effort. This is fantastic.

React keeps track of your app, and those changes in a tree like structure in the “virtual dom”. The problem comes in, in that it often isn’t sure what components in that tree need to be re-rendered, and so, far too many of them update. Luckily for us, they do provide us a way to check and filter the data we want to “react” to in our components via the shouldComponentUpdate() lifecycle method.

Option A: Use PureComponents

More recently, react has introduced “Pure Components”, which incorporate a mechanism to reduce this problem by doing a shallow compare, while this is far from a final solution, it will certainly help you along the way.

class Greeting extends React.PureComponent {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

Note from the docs:

React.PureComponent’s shouldComponentUpdate() only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences

read more about pure components in the docs here:

Option B: Use Deep Equality Where you need it

Pure component is great if your data structure is simple coming in. You may find that your data structure coming into a component may be nested and complex.

Heres an example, note I’m using the underscore.js library to do the deep object comparison for me, you could write your own method, or use a similar function from another library if you prefer.

//... inside your React.Component
shouldComponentUpdate(nextProps, nextState) {
return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
}

Its worth saving this little snippet, as it’ll come in quite handy, often.

Option C: Only Update on Very Specific Changes

You may have a component or two that only need to update if a particular property, or combinations of properties, is true, (such as visibility, or an open state). In these cases you can check for a very specific prop change, and only re-render when it changes.

//... inside your React.Component
shouldComponentUpdate(nextProps) {
return !(this.props.name === nextProps.name);
}

Step 2: Narrow the updates you connect to with Redux

Not using redux? skip this step and go get started with redux, it’ll greatly improve your app, your life, and your mom's opinion of you. Once you’ve got your sh@#$ together, come back and read this again.

Be Specific, about what piece of state you‘re bringing into your component from the store, selectors are the key here. These can be written inline, though I generally like to extract them into functions, for instance:

// in a reasonable place, ie: ../selectors/common.js
export const selectById = (items = [], id = '') => (id ? items.filter(item => items.id === id)[0] || {} : {});

// in your connected component file
const mapStateToProps = state => ({
design: selectById(state.designs, state.cart.designId)
});

Step 3: Improve Selector Performance with Reselect

Reselect is a small additional dependency, that allows us to create selectors, that cache the results, and only if a specified selection from the store changes, will it rerun the selector function.

This is particularly useful for computed properties, such as the total, subtotal, and tax calculated on a list of shopping cart items, the dimensions of a room given each individually or the weighted average across a dataset. Just like a database, we generally want to keep computed properties out, and cache them instead, Reselect allows us to do just that, and then only update our components exactly when we want them to be updated.

Step 4: Master chrome dev tools and dig in.

As I said in the caveats, this is only the beginning. As your app grows in complexity, there will be a myriad of opportunities and threats regarding performance. If you can master your development tools, you’ll have a clear insight into whats going on in your UI, and where to get started fixing it.


Conclusion

This is really just a start, if you’re serious about your UI performance the best place to get started is with the official docs, on optimisation, here:

Sigma Digital

My name is Paul van Zyl. I’m the principal consultant at Sigma Digital, a small specialised, decentralised product design and development team. If you’re looking for a partner who can actually deliver on your next innovative project, help you and your team up your game, or just want to say hello, get in touch here: paul [at] sigmadigital.io