react · rendering3 min
why did this render?
what makes a React component render again, what React actually does when it does, and how to get the browser to tell you which ones rendered and why.
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
useStateoruseReducer. - a context it reads with
useContextgets 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.
#what a render is
a render doesn't mean the screen changed. React splits an update into three steps:
- trigger: some state changes.
- render: React calls your components to find out what the screen should look like now, and compares the result with last time.
- commit: React changes the DOM, only where the two were different.
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.
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.
// 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, 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.