Few Tips to Optimizing Performance of React Project
Tags: react, hooks, performance
Published: 2020-02-02 00:30:27

<blockquote>
In this article, I will introduce a few different ways to optimize the performance for React apps. And use an example to demonstrate how to choose a proper solution when doing the optimization.
</blockquote>

Before we start, let's look at an example.
Here we have 24 checkboxes to allow the users to select the time they want. 
{% codesandbox react-performance-example-1-bm7s9 %}

The problem of this example is that every time when the user clicks the checkbox, all the checkboxes will also re-render.

So, how can we solve this problem?


## React DevTools Profiler
Before we start optimizing our application, we need to know how to identify the performance issues of our application?
react-dom 16.5+ and react-native 0.57+ provide enhanced profiling with the [React DevTools Profiler](https://reactjs.org/docs/optimizing-performance.html#profiling-components-with-the-devtools-profiler). 

Using React DevTools Profiler is simple, click the record button at the top-left corner, interact with our application and stop the record by clicking the same button again. Then we ended up having the result to identify the issues.
![react-profiler](https://i.imgur.com/D400grl.gif)

When we check the flamegraph through React DevTools Profiler, we can see the un-necessary re-render.
![react-profiler](https://i.imgur.com/nNxJPZ6.png)

Now we know the problem, let's try a few different solutions for that.

## PureComponent
First, we can try the simplest solution - [PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent), we just need to change our class extend from component to PureComponent, then React will do the rest for us.

```js
// before
export default class CheckBox extends React.Component {
  ...
}

// after
export default class CheckBox extends React.PureComponent {
  ...
}
```

But after we change to `PureComponent`, we can see it didn't prevent the unnecessary re-render. The reason is that we create a new handleToggle function every time. So even we apply `PureComponent` it still re-render all the CheckBox components when App component re-render.


## ShouldComponentUpdate
Because `PureComponent` didn't work. So now we have to do the check on our own. We can use [ShouldComponentUpdate](https://reactjs.org/docs/react-component.html#shouldcomponentupdate) to block un-necessary render.

```js
shouldComponentUpdate(nextProps) {
  const {value, isChecked} = nextProps;
  return this.props.value !== value || this.props.isChecked !== isChecked
}
```

Now, when we check React DevTools Profiler again, we will see only the click checkbox will re-render.
![react-profiler](https://i.imgur.com/5YMlwfN.png)

## React.memo
If we want to use function components rather then class, we have another option - [React.memo](https://reactjs.org/docs/react-api.html#reactmemo).
`React.memo` will do the same check as `PureComponent`. But it allows us to pass the second parameter to do the custom check similar with `ShouldComponentUpdate`. But we need to notice that the return value should be opposite with `ShouldComponentUpdate`.

```js
export default React.memo(CheckBox, (prevProps, nextProps) => {
  return prevProps.value === nextProps.value && prevProps.isChecked === nextProps.isChecked
});
```

## useMemo
Another solution for function components is to use hooks - [useMemo](https://reactjs.org/docs/hooks-reference.html#useMemo).

```js
export default function CheckBox ({value, isChecked, handleToggle}){
  return React.useMemo(() => {
    return (
      <div>
        <label>
          <input type="checkbox" value={value} checked={isChecked} onChange={handleToggle} />
          {value}
        </label>
      </div>
    )
  }, [value, isChecked]);
}
```
Although this can help us prevent un-necessary re-render. We will see an error from [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks).
![eslint-react-hooks](https://i.imgur.com/U5qrQXD.png)

The `eslint` will remind us to add `handleToggle` into our dependency array. But we can't because that's something we have to ignore to prevent un-necessary re-render. We can easily use `eslint-disable` to prevent this error. But actually, this error message does point out an important problem.

Although most of the above solutions (except `PureComponent`) can help us optimize the performance. But those custom logic also make the code harder to maintain and might bring some potential bugs.
Let's say when another team member adds a new prop - `isDarkMode` for the Checkbox component, if he or she forgets to adjust the custom logic in `ShouldComponentUpdate` or `React.memo`, then the dark mode won't work because Checkbox will not re-render when `isDarkMode` prop change.

So, how can we solve this problem?


## useCallback
A better way to solve this performance problem is to prevent creating a new `handleToggle` function every time.
We can change our App component into a class component. Or use another hook - [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback) to do the job.

```js
const handleToggle = useCallback(targetTime => {
  setTimeCheckboxes(timeCheckBoxes => {
    return timeCheckBoxes.map(({ time, isChecked }) => ({
      time,
      isChecked: targetTime === time ? !isChecked : isChecked
    }));
  });
}, []);
```

Due to we won't create a new `toggle` function every time now. We just need to apply `PureComponent` on Checkbox. Then we can prevent the un-necessary re-render without adding any custom logic into our codebase.

{% codesandbox react-performance-example-2-z805v %}


## Measurement
Furthermore, rather than just understanding how to optimize our application, we also need to know how to measure the performance of our application.

### React Profiler
React provide a component to help us achieve that - [Profiler](https://reactjs.org/docs/profiler.html).
Just simply wrap our App component with `Profiler`, then we can get the information we need.

```js
<Profiler id="app" onRender={onRenderCallback}>
  <div className="App">
    ...
  </div>
</Profiler>
```

`onRender` prop will pass those information into our callback function. So we can print the information we need.

```js
function onRenderCallback(
  id, // the "id" prop of the Profiler tree that has just committed
  phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
  actualDuration, // time spent rendering the committed update
  baseDuration, // estimated time to render the entire subtree without memoization
  startTime, // when React began rendering this update
  commitTime, // when React committed this update
  interactions // the Set of interactions belonging to this update
) {
  // Aggregate or log render timings...
}
```

Now that we can know the difference before and after the optimization.
- Before
  ![profiler](https://i.imgur.com/YRnoDaW.png)

- After (apply `useCallback` and `PureComponent`)
  ![profiler](https://i.imgur.com/dBc3MiS.png)

### Chrome DevTools: Performance
Another option is to use Chrome dev-tools. We can select `Performance` tab and start record just like what we did on React DevTools Profiler.
(here I slow down the CPU to make it easier to identify the performance problem, we can also simulate a slower network if we need)
![chrome-dev-tools](https://i.imgur.com/oWSEbi2.png)

Then we can see the result like this.
- Before: 152.72ms, 132.22ms, 204.83ms
  ![chrome-dev-tools](https://i.imgur.com/WXGV6EH.png)
- After (apply `useCallback` and `PureComponent`): 15.64ms, 18.10ms, 12.32ms
  ![chrome-dev-tools](https://i.imgur.com/maX13mB.png)


---

## Conclusion
React provide many APIs and tools to help us optimize our application. While we try to optimize the performance, we need to choose the solution wisely, this will make sure our code is still easy to maintain after we improve the performance of our application.

--

## Reference
- [Optimizing Performance](https://reactjs.org/docs/optimizing-performance.html)
- [PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent)
- [ShouldComponentUpdate](https://reactjs.org/docs/react-component.html#shouldcomponentupdate)
- [React.memo](https://reactjs.org/docs/react-api.html#reactmemo)
- [useMemo](https://reactjs.org/docs/hooks-reference.html#useMemo)
- [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback)
- [Profiler](https://reactjs.org/docs/profiler.html)