# why did this render?

2021-05-21 · 3 min read · #react · #rendering

Source: https://jopcmelo.com/writing/why-did-this-render

every React app gets slow somewhere eventually, and the first question is always the same: **why did this render?**

## three reasons

a component renders again when:

- its own state changes, through `useState` or `useReducer`.
- a context it reads with `useContext` gets a new value.
- its parent renders.

the last one surprises people. a child renders when its parent does, even if none of its props changed. "its props changed" isn't a fourth reason: props only change because the parent rendered with new ones. React's docs put it as [a state update in the component or in one of its ancestors](https://react.dev/learn/render-and-commit#step-1-trigger-a-render).

*(sketch: a tree of components: App at the top, Header and List under it, two Items under List. when App's state changes, a tally of renders goes up beside every one of them at once, over and over)*

## what a render is

a render doesn't mean the screen changed. React splits an update into [three steps](https://react.dev/learn/render-and-commit):

1. trigger: some state changes.
2. render: React calls your components to find out what the screen should look like now, and compares the result with last time.
3. commit: React changes the DOM, only where the two were different.

*(sketch: trigger, then render, then commit. under trigger: state changed. under render: call components, compare. under commit: touch only what changed)*

**a render that changes nothing never touches the DOM.** it still costs the time it took to call your components, and that's what you feel when a page with a lot of them gets slow.

## keys

the compare step is where lists get interesting. say there are 1,000 items and someone deletes the third. React lines up the old list and the new one by [key](https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key).

with the index as the key (or no key, which is the same), React matches items by position. the old item 3 slot now holds item 4, the old item 4 slot holds item 5, and so on to the end, so every row from there down gets new text. I measured it with React 19 in jsdom: removing item 3 of 1,000 made 997 text changes in the DOM. and if each row has an input, the third input keeps what you typed into item 3, now next to item 4's label.

with a stable key, like an id, React knows which item left. **one row removed, zero text changes**, and every input stays with its own item.

*(sketch: two lists. with key={index}, item 3 is gone and every row after it is rewritten. with key={item.id}, only item 3 is removed)*

```jsx
// matched by position: every row after the deleted one gets new text
items.map((item, index) => <li key={index}>{item.name}</li>);

// matched by identity: only the deleted row goes
items.map((item) => <li key={item.id}>{item.name}</li>);
```

## let React DevTools tell you

you don't have to guess any of this. [React Developer Tools](https://react.dev/learn/react-developer-tools), for Chrome, Firefox and Edge, adds two tabs:

- Components: the tree, with each component's props, state and hooks, and which component rendered it. turn on "Highlight updates when components render" in its settings, and every render flashes on the page.
- Profiler: record a few clicks, stop, and see every commit and how long each component took. turn on "Record why each component rendered while profiling" first, and **it answers the question for you**: for each render, it says whether a hook changed, which props changed, or that the parent rendered.
