React is an amazing JavaScript library for building user interfaces and is well suited for developing large and small apps built to get the highest performance possible today on the web. But sometimes we as a developer end up doing things that result in the poorly performing app.
In this post, I have put together React-specific best practices to boot runtime performance. Also, apart from React. My intention in this post is just to list down the best practices and avoiding the detailed explanation thus keeping the post small.
Identify Performance Issues
In react-dom 16.5+ the React team has provided enhanced profiling capabilities in DEV mode through the React DevTools. This is always the first tool that I grab when investigating potential performance optimizations. You can find them here:
-
React DevTools Profiler.
- Profiling the performance of a React app can be a difficult and time-consuming process. By installing the React developer tools, you can record and interrogate the performance of individual components in your app and make this process much easier.
- The React DevTools Profiler is usually the first place I will look. There is an official blog post and video walkthrough that goes into great detail on using the profiler to gather performance data.
-
React DevTools Update Highlighting
- React maintains a virtual DOM that it reconciles against to determine which parts of the UI need to re-render based on props or state changing. This is great, but it also means we don’t really know which parts of our application are updating at any given time. In the React DevTools, there is a setting you can turn on that will visually highlight elements on the screen as they render (or re-render).
3. Why Did You Render
- Sometimes you know a certain React component should not be re-rendered unless there’s a very good reason. But how can you identify it? Let me share an incredible utility called @welldone-software/why-did-you-render this will notify you of to cause of the re-rendering. After configuring your console will fill with information to help you track when and why certain components re-render.
4. shouldComponentUpdate
- The shouldComponentUpdate() method is the first real life cycle optimization method that we can leverage in React. We can look at our current and new props & state and make a choice if we should move on.
- The purpose of shouldComponentUpdate is to indicate if render should be called. In your case, some parent component has rendered and indicated it wanted to also render an instance of your child component.
- shouldComponentUpdate is your opportunity to short-circuit that render and say 'don't bother, nothing changed down here'.
- Now, to your question, "why was it even called since nothing changed"? React does not compare the old and new props itself. You can get a mixin to do it for you, (ie. PureRenderMixin), but by default React just lets the render run.
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
}
- The reason React doesn't do the comparison itself is for a couple of reasons. Firstly, the performance savings of skipping render may be negligible compared to analyzing props and state. Since React's render mechanism is already optimized to avoid unnecessary DOM manipulation it can just assume the component needs to update and expect reasonable performance. Secondly, doing the comparison is not exactly straight-forward. Is your prop a primitive?, an Immutable? an array? a complex object? will a deep comparison be necessary?
- React's model is "We will render everything asked by default. If you want something to opt-out for performance, then go ahead and tell us by implementing shouldComponentUpdate".
5. React.PureComponent
- When a class component extends React.PureComponent base class then React treated the component as a Pure component. The major difference between React.Component class and React.PureComponent is the implementation of shouldComponentUpdate(). In React.Component shouldComponentUpdate() will always return true on the other hand in React.PureComponent will compare the current state and props with the new state and props.
import React, {PureComponent} from ‘react’;
export default class Test extends PureComponent{
render(){
return ‘’;
}
}
- But, the point is React.PureComponent’s shouldComponentUpdate() only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extend PureComponent when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed. Or, consider using immutable objects to facilitate fast comparisons of nested data.
- Furthermore, React.PureComponent’s shouldComponentUpdate() skips prop updates for the whole component subtree. Make sure all the children components are also “pure”.
6. React.memo
- React.memo provides similar functionality if you use function components instead of class-based components.
- React.memo is a higher order component.
- If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
- React.memo only checks for prop changes. If your function component wrapped in React.memo has a useState or useContext Hook in its implementation, it will still rerender when state or context change.
- By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
7. Virtualize large lists with react-window
- There may be times where you need to display a large table or list that contains many rows. Loading every single item on such a list can affect performance significantly.
- List virtualization, or "windowing", is the concept of only rendering what is visible to the user. The number of elements that are rendered at first is a very small subset of the entire list and the "window" of visible content moves when the user continues to scroll. This improves both the rendering and scrolling performance of the list.
- react-window is a library that allows large lists to be rendered efficiently. Here is a beautiful article on this topic.
(Sidenote: If you are interested in productivity, tech, and product topics, follow me on Twitter where I post about these things in an easy and fun way.)
Top comments (2)
This is a good one.
Thanks, good advice. i'm looking to improve my React skills