How to stop re-rendering lists in React?
Tags: react
Published: 2021-07-14 08:14:00

You have a list of components in React. The parent holds the state and passes it to the list items. Every time you update the property of one of the components in the list, the entire list re-renders. How to prevent that?

<img alt="List of react components. Every component in the list re-renders, even though the property updates for a single component." width="680" height="334" src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tjk7gkqol1g0oqunp3o.gif">

---

## Components always re-render

First, let's simplify our example by removing all props from the `Item`. We will still update the parent state but won't pass any props to list items.

<img alt="List of react components that rerenders when parent renders" width="680" height="301" src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zohv89wsxc743g6slx25.gif">

There is a common misconception that a React component will not re-render unless one of its properties changes. This is not true:

> React does not care whether "props changed" - it will render child components unconditionally just because the parent rendered!
>
> <cite>Mark Erikson - [A (Mostly) Complete Guide to React Rendering Behavior](https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/#standard-render-behavior)</cite>

If you don't want a component to re-render when its parent renders, wrap it with [memo](https://reactjs.org/docs/react-api.html#reactmemo). After that, the component indeed will only re-render when its props change.

```jsx
const Item = memo(() => <div>Item</div>)
```
 
<img alt="List of react components that doesn't rerender when parent renders" width="680" height="301" src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qh67x2mzfv72hapxq8h6.gif">

---

## Applying memo to our problem

Let's get back to our initial example and wrap `Item` with `memo`. Here is a slightly simplified code.

```jsx
const Item = memo(({id, value, onChange}) => {
  return (
    <input
      onChange={e => onChange(id, e.target.value)}
      value={value} />
  )
})
```

<img alt="List of react components. Every component in the list re-renders, even though the property updates for a single component." width="680" height="334" src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tjk7gkqol1g0oqunp3o.gif">

It doesn't work. We still have the same problem. But why?

**If the component wrapped with `memo` re-renders, it means that one of its properties changes**. Let's figure out which one.

---

## Memoizing properties

We know from looking at the state that `value` only changes for one item in the list. The `id` property is also stable. So it must be `onChange` property that changes. Let's check the `Parent` code to see how we pass the props.
  
```jsx
const Parent = () => {
  const [items, setItems] = useState([
    { value: '' },
    { value: '' },
    { value: '' }
  ])
  return (
    <div>
      {items.map((item, index) => (
        <Item
          key={index}
          id={index}
          value={item.value}
          onChange={(id, value) =>
            setState(state.map((item, index) => {
              return index !== id ? item : { value: value }
          })}
          />
      )}
    </div>
  )
}
```

Here is our problem:

```jsx
onChange={(id, value) =>
  setState(state.map((item, index) => {
    return index !== id ? item : { value: value }
})}
```

**Anonymous functions will always get a new reference on every render**. This means that `onChange` property will change every time `Parent` renders. To prevent that, we need to memoize it with [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback). Let's do that:

```jsx
const Parent = () => {
  ...

  const onChange = useCallback((id, value) => {
    setItems(items.map((item, index) => {
      return index !== id ? item : { value: value }
    }))
  }, [items])

  return (
    <div>
      {items.map((item, index) => (
        <Item
          key={index}
          id={index}
          value={item.value}
          onChange={onChange}
          />
      )}
    </div>
	)
}
```

<img alt="List of react components. Every component in the list re-renders, even though the property updates for a single component." width="680" height="334" src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tjk7gkqol1g0oqunp3o.gif">

It still doesn't work - every component re-renders.

This happens because we put `items` as a dependency for `useCallback` . Every time `items` update, `useCallback` returns a new reference of the function. This causes `onChange` prop to change, therefore updating every component in the list.

To fix this, we need to stop relying on `items` as a dependency. We can achieve that with a [functional state update](https://reactjs.org/docs/hooks-reference.html#functional-updates):

```jsx
 const onChange = useCallback((id, value) => {
    setItems(prevItems => prevItems.map((item, index) => {
      return index !== id ? item : { value: value }
    }))
  }, []) // No dependencies
```

<img alt="List of react components. Only one component updates when we update a property of this component" width="680" height="334" src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pq2kwkzl9eirdqzro7kq.gif">
 
Now, the only property of the `Item` that changes is `value`. And since we only update one `value` at a time, it prevents other components in the list from re-rendering.

---

## Should I do that for every list?
You don't have to optimize every unnecessary re-render in React. React render is quite performant. It only updates DOM when needed. And `memo` comes with a small performance cost as well. Optimize it when you have a lot of items in the list and your render function is expensive.

> I would assume that the same general advice applies for React.memo as it does for shouldComponentUpdate and PureComponent: doing comparisons does have a small cost, and there's scenarios where a component would never memoize properly (especially if it makes use of props.children). So, don't just automatically wrap everything everywhere. See how your app behaves in production mode, use React's profiling builds and the DevTools profiler to see where bottlenecks are, and strategically use these tools to optimize parts of the component tree that will actually benefit from these optimizations.
>
> <cite>Mark Erikson - [When should you NOT use React memo?](https://github.com/facebook/react/issues/14463#issuecomment-448829902)</cite>

- [Before you memo](https://overreacted.io/before-you-memo/) - Dan Abramov
- [Fix the slow render before you fix the re-render](https://kentcdodds.com/blog/fix-the-slow-render-before-you-fix-the-re-render) - Kent C. Dodds
  
---

Originally published at [alexsidorenko.com](https://alexsidorenko.com/blog/react-list-rerender/)