React Keys and ReactDOM
August 17, 2021
2 min read
Keys help identify React Components
Traditionally when we are taught about keys in React, it is in the context of rendering a list of elements. Take this simple example:
let users = [...] // some array of users...users.map(user => <User name={user.name} />)
When rendering this array of <User>
components, React has no way of telling each of the elements apart in the array. The key uniquely identifies each component, so React can uniquely track the component across re-renders.
users.map(user => <User key={user.id} name={user.name} />)
Index != a good key
Now, if we don’t specify a key, then the only information React has to identify a specific element is its index in the array. So React will use the index as the key.
// no key specifiedusers.map(user => <User name={user.name} />)// being explicitusers.map((user, index) => <User key={index} name={user.name} />)
Why is this a bad idea? Well the requirements of the key are to:
- uniquely identify a component
- stay the same across re-renders (so React can track the component)
Using an index as a key only works only if you fix the indices for each of the elements, that is the array stays the same. If you change the order, or add / remove elements from the array, you can get weird bugs, because now the index doesn’t fit the requirements of a valid key.
E.g. say we have an array of <User>
components, each with their own state about that user. React tells them apart by their position in the array, so when it comes to looking up state it will find the user state associated with that position.
See the issue? It’s associated by position (the key) not the component.
So if I add a new user to the start of the array, then every user shifts along one in the array.
;[<User name="Bob" />, <User name="Charlie" />][// add Alice((<User name="Alice" />), (<User name="Bob" />), (<User name="Charlie" />))]
In the example above, Alice slots into Bob’s position so inherits the state associated with Bob! Oh dear.
Using Keys to force a re-render
Keys aren’t limited to components in a collection (like an array), in fact every component has an implicit key associated with it that React uses to track it. Theres’s a 1:1 mapping from keys to components. So different key => different component!
We can thus force the ReactDOM to re-render by changing the key of a component. When the key changes, React realises that this is no longer the same component and thus resets the state. So in my website for example, I can get React to re-render whenever the active tab changes.
<TabSection key={activeTab} />
Note the difference between key
and props
, if I had passed the value in via props
, e.g. props.tab
, then React would have re-rendered the same TabSection
component, whereas passing in as key
re-renders with another TabSection
component associated with the new key.
<TabSection tab={activeTab} />
In essence we’re tracking multiple TabSection
components, one for each of values activeTab
can take, and each with completely independent state.