Optimizing Re-renders in React

Optimizing Re-renders in React

In article I will show you a demo of how you can optimize component re-renders in React. We will be needing React Profiler extension for this demo, if you are new to React profiler you can check this post on the react blog and install React developer tools. This article covers how you can optimize React components relying on the following

  • useCallback

  • React.memo

  • React.memo + custom comparison function

- other optimizations: useMemo, windowing.

Before we dive right into the demo, let'stake a quick look at when the useCallback hook provided to us by React comes in handy:

  • useCallback: from the docs -

    useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).

Don't know if that made enough sense?, well, just remember that every time a component re-renders, the functions defined in it usually our event callbacks - handleChange, handleSearch e.t.c whatever you usually call them are re-created and therefore passed down to the child components of the component being re-rendered, this child components detects that a callback function prop being passed to it changed, and therefore also re-renders. This re-render of the child component might be an unnecessary re-render as a result of the callback function being passed down to it changing, since its parent component re-rendered. using useCallback, you can tell React to give you exactly the same callback function across re-renders and only re-create that callback function when a certain data that the callback is dependent on, changes. This way you can prevent unnecessary re-renders.

yeah, so lets jump on to our quick demo, to better explain this, here is a link to the github repo. on the main branch, you will find the unoptimized code, which just generates random array of objects with various user data and displays them in different inputs. The problem is every time there is a single keystroke on one of the input box for a particular user, "every single input on the page re-renders", which is quite a lot. we will optimize this code, so that only the input being changed actually re-renders.

optimain1.gif

notice that our entire list of users and their details re-render after just one input for a particular user field changes, and the reason for this re-rendering across all our lists was because our props - "handleChange" is re-created as the parent component re-renders, therefore a new "editUser" function is passed to all the user rows as a prop called "handleChange", causing a re-render. In other for us to optimize this code, we could wrap "editUser", which is a callback from the parent component with useCallback and specify its dependencies in the dependency array, so it doesn't change on every re-render.

editUser.png

let's profile our app again after wrapping editUser in useCallback and passing it down as a prop called handleChange.

reactOptimizationUseCallback.gif

It actually seems as if our useCallback optimization had no effect as we still ended up having as many re-renders as it were before adding the useCallback hook, the only difference this time is the reason for the re-render which is that, "The parent component re-rendered.", which is a valid react re-rendering behavior. whenever a parent component re-renders, its direct children also re-renders as a result of the parent re-render. To change this behaviour we will need to use React.memo.

React memo

React.memo is a high order component (a component that takes in a component and return a new, usually modified component). As stated in the react docs.

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.

This comes in handy in this case, since the User component now receives the same props and only re-renders because the parent component re-rendered. wrapping the User component in React.memo prevents these unnecessary re-render of most of the User component being rendered, except for the user row being edited.

Our user component then looks like this

UserCOmp.png

and Profiling our app again, we get this

react optimization(useCallback and react memo).gif

Notice that the only User component that re-rendered is the user row that we are currently editing. we can further can optimize our app to only re-render the input box that is being edited in a particular user row rather than all three input boxes in that row. In order for us to do this, we will need to wrap our TextInput with React.memo and a custom comparison function.

React memo + custom comparison function

according to the react docs, React memo will, by default:

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.

TextInput.png

we have successfully optimized our app to only re-render the input box, that is currently being edited.

A note on optimization

React is fast and most times there will be no significant gain in optimizing re-renders unnecessarily, they are however certain times when optimizing re-renders could be of significant benefit, so you would mostly not need to worry about optimizing re-renders unless you've observed a slow response in your application, for example in the case of this demo in which we had several components re-rendering unnecessarily on a single key stroke, which in turn was significantly affecting the speed of the application.
There also other techniques/tools that come in handy in order to optimize the performance of your react apps, such as useMemo and windowing.

useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

useMemo was used in the User component to memoize the calculated value of totalTimeLivedInDays, this isn't necessarily an expensive calculation but was included to show how the hook is used.

usememo.png

while windowing is a concept that can be used to optimize and increase the speed and rendering of a large list. This demo does not show how to do this, but you can checkout this react library if you wish to apply this concept.