Don't Surrender to Extraneous React Re-Renders
Tags: react, javascript, performance, redux
Published: 2019-11-07 19:29:21

---
title: Don't Surrender to Extraneous React Re-Renders
published: true
description: 6 ways to avoid unnecessary re-renders in React
tags: react, javascript, performance, redux
cover_image: https://thepracticaldev.s3.amazonaws.com/i/q1d2hcdx3jbkqbjn45c0.jpg
---

After learning about and monkeying around with this fantastic React tool [`why-did-you-render`](https://github.com/welldone-software/why-did-you-render) for about a week, I realized there was a lot that I didn't realize or understand about how React determines when to re-render a component. Here are 6 of the most helpful things I learned during this adventure.

## 1. Use React.memo for pure functional components

With [React hooks](https://reactjs.org/docs/hooks-intro.html), it's easier than ever to use functional components rather than class components. Larger and/or more complex components can be written as functions instead of classes. However, vanilla functional components re-render with every change to props, and when dealing with a large or complex component, that might not be necessary. 

Enter [`React.memo`](https://reactjs.org/docs/react-api.html#reactmemo). This makes a functional component behave similar to extending `React.PureComponent` -- namely, that it will do a shallow comparison of props on any prop change, and only re-render if previous props shallowly equal new props.

## 2. Pass in a comparison function for doing deep compares

Shallow comparison might not do the trick though. After all, maybe one of the props is an array of strings. If that array is generated on the fly somehow, for example by taking something from state and using `map` or `filter` to get only certain ones, even if the array contents hasn't changed, the new prop will be a new array, so `prevProps.arrayProp === this.props.arrayProp` will be false, and the component will re-render unnecessarily.

Luckily, `React.memo` takes a second argument that will be used to compare the props. So if there are limited props that can be compared deeply, that can avoid some unnecessary re-renders. A few notes about this:
- The docs say this is not guaranteed to prevent re-renders. However, anecdotally I have noticed fewer re-renders using this approach. 
- Depending on how large or "heavy" the component is, and depending on how complex the props are, it's a good idea to determine whether it will be more performant to re-render or do a deep compare.
- This is more or less analogous to the `shouldComponentUpdate` lifecycle method on `React.Component`, only in reverse (e.g. if `shouldComponentUpdate` returned true, the component would re-render; whereas if this passed-in function `areEqual` returns true, the component _does not_ re-render.)

## 3. Only update state if it has changed

As you can see, the name of the game in reducing re-renders in general is to avoid props changes. Sometimes that will mean adding a bit more complexity elsewhere. For example, on our team, we like simple cases in reducers, such as this:
```javascript
  case 'DOMAIN/UPDATE_ARRAY_PROP': {
    const { propName, arrayProp } = action;
    return Object.assign({}, state, {
      ...state, 
      [propName]: arrayProp
    })
  }
```
But, if `state[propName]` is deeply equal to `arrayProp`, we are reassigning that property even though it isn't actually changing. And as we just learned, reassigning the property, particularly when dealing with array and object props, creates a new array or object which will cause shallow comparisons to fail. 

Instead, we should check if an UPDATE action is actually going to update, or if the updated values are the same as what's currently in state. If they are the same, don't update them and return state as-is to avoid the re-render. The above example, reworked (using [`lodash/isEqual`](https://lodash.com/docs/4.17.15#isEqual)):
```javascript
  case 'DOMAIN/UPDATE_ARRAY_PROP': {
    const { propName, arrayProp } = action;
    // Add this guard!
    if (isEqual(state[propName], arrayProp)) return state;

    return Object.assign({}, state, {
      ...state, 
      [propName]: arrayProp
    })
  }
```

To further illustrate this, here's an example updating an object's property. 

With extraneous re-renders:
```javascript
  case 'DOMAIN/UPDATE_OBJECT_NAME': {
    const { objectName, newName } = action;

    return Object.assign({}, state, {
      ...state,
      [objectName]: {
        ...state[objectName], 
        name: newName
      }
    })
  }
```

Optimized:
```javascript
  case 'DOMAIN/UPDATE_OBJECT_NAME': {
    const { objectName, newName } = action;
    // Add this guard!
    if (state[objectName].name === newName) return state;

    return Object.assign({}, state, {
      ...state,
      [objectName]: {
        ...state[objectName], 
        name: newName
      }
    })
  }
```


## 4. Avoid data conversion in selectors

Same problem, different symptom. When using selectors, avoid doing any data conversion if possible. This includes using `map` and `filter`. I have experience with selectors becoming a kind of repository of helper functions that do a lot of `map`ping and `filter`ing. Using tools like [`reselect`](https://github.com/reduxjs/reselect) can help with this by memoizing the return values of the selectors. 

Even so, some selectors might be better moved to helper functions, imported into the functions, and used to `map` or `filter` values pulled directly from state. Because a selector that pulls from state and then `map`s or `filter`s will return a new array and re-render every time, whereas using a helper function in the component would have the component only re-render when that value in state has changed.

## 5. Get only what is needed from state

In selectors, fetch only what is needed in the component. For example, if I only want to check the count of some array of objects, I don't want to load the whole array into props, I just load the count for simpler shallow comparison.

## 6. No anonymous functions as props
 
I have seen and done this many times before realizing it was problematic:
```javascript
<SomeComponent
  onError={() => console.error('BAD')}
/>
```
Every render of `SomeComponent` will compare that function against its previous iteration, and since it's anonymous, it will be effectively a different function each time, resulting in shallow prop comparison failure. 

Instead, define functions outside the component and then pass in the named function:
```javascript
const logError = () => console.error('BAD');
<SomeComponent
  onError={logError}
/>
```
There are also some more complicated and helpful examples in the [issue tracker](https://github.com/welldone-software/why-did-you-render/issues/33) for why-did-you-render.

## Conclusion

Remember that React itself seems generally very performant, so it's important to try not to get bogged down in wiping out all unnecessary re-renders. With small enough components, even if they re-render all the time, it likely won't have noticeable affects on app performance. For me, I choose to focus on the big heavy component re-renders and any low-hanging fruit for the smaller components, and I don't sweat the other stuff.

######_Image credit [Louis Hansel](https://unsplash.com/@louishansel?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)_