If I have some elements to render in a row, e.g.:
<>
<div className="foo">a</div>
<p>b</p>
<button>some button</button>
<CustomElement myAttr="attr" />
</>
Do I need to add keys for these elements? If not, how is React going to handle the following condition:
<>
{someCondition && <div className="foo">a</div>}
<p>b</p>
<button>some button</button>
<CustomElement myAttr="attr" />
</>
Assuming that someCondition
will be false
at first and true after some event, will it cause a re-render on its siblings <p>, <button>
and CustomElement
?
Or is React simply adding keys automatically for these elements? Thanks!
EDIT: In the second snippet the <div />
element should only show if someCondition
is true. All the other three elements should always be shown.
What I actually wanted to ask is that without manually keying the elements, how it React going to know if one element is the same as the other when updating? I'm aware that in the docs it says keying tells that the element is an already existed element.
So I wander that if not giving React keys on normal non-list elements, will React know if they are the same by automatically assigning them a key? Or does React have a special technique to prevent rerenders on normal elements?
If I have some elements to render in a row, e.g.:
<>
<div className="foo">a</div>
<p>b</p>
<button>some button</button>
<CustomElement myAttr="attr" />
</>
Do I need to add keys for these elements? If not, how is React going to handle the following condition:
<>
{someCondition && <div className="foo">a</div>}
<p>b</p>
<button>some button</button>
<CustomElement myAttr="attr" />
</>
Assuming that someCondition
will be false
at first and true after some event, will it cause a re-render on its siblings <p>, <button>
and CustomElement
?
Or is React simply adding keys automatically for these elements? Thanks!
EDIT: In the second snippet the <div />
element should only show if someCondition
is true. All the other three elements should always be shown.
What I actually wanted to ask is that without manually keying the elements, how it React going to know if one element is the same as the other when updating? I'm aware that in the docs it says keying tells that the element is an already existed element.
So I wander that if not giving React keys on normal non-list elements, will React know if they are the same by automatically assigning them a key? Or does React have a special technique to prevent rerenders on normal elements?
Share Improve this question edited Apr 11, 2024 at 5:33 Drew Reese 203k17 gold badges235 silver badges266 bronze badges asked Sep 8, 2022 at 1:44 Sam ZhangSam Zhang 3906 silver badges20 bronze badges 1- One thing I have discovered is that we need to add keys only if we are rendering an array of components. Here jsfiddle.net/bqvod1n9 gives error for keys in the console, but when render doesn't have components as an array jsfiddle.net/z8vqt9Lw no keys array is shown. – user31782 Commented Sep 15, 2022 at 6:52
6 Answers
Reset to default 11 +50There are some very good answers here. But I didn't see anyone mention that you can actually pass keys to elements that are not from an array. And it has a genuine use case. You can set the key to a different value if you want to force a component to be remounted.
You should read through react reconciliation for a better explanation, but I'll just write up a short one. When a re-render occurs, react creates a virtual DOM that will have the updated data. Then it does a comparison between the old DOM that is rendered on the page, we'll call it the real DOM, and the virtual DOM, which is not rendered, it's held in memory. In order to reduce complexity, react does not compare all the nodes of the real and virtual DOM. Let's take three cases
Case 1: Two nodes of different types
For example, an <a>
to <span>
, then the whole subtree with the root as that element is re-rendered. This means that going from
<div>
<Counter />
</div>
to
<span>
<Counter />
</span>
will destroy the Counter
component and remount a different instance. This will run componentWillMount
and useEffect(() => {}, [])
.
Case 2: Two DOM elements of the same type
Let's say you have two div
s with different attributes, then react knows only to modify the changed attribute. Meaning it won't remount the div
.
Case 3: Two react components of the same type
In this case, react will just update the props of the component, and then the component's render method will run the reconciliation algorithms again on its children.
When react is recursing on children, it will just compare each element one by one and generates a mutation when there is a change. This means that adding an item to a list at the end is really efficient. But adding one to the start is very inefficient, as it will generate mutations for every item in the list. Here is where key
s come in. When we add a key, we are telling react that as long as the elements have the same key, it is the same element. This is why using the index as a key or using random keys is very bad for performance. React will be forced to recreate all the elements on every render.
And finally coming to my first point. Using key
s for non-list elements.
Let's say you have a profile component, and you want to reset all the states when the userID
changes.
export default function ProfilePage({ userID }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
return (
<Profile
userId={userID}
/>
);
}
Instead of using an effect like this, you can just set a key on the Profile
component.
<Profile
userId={userID}
key = {userID}
/>
Using an effect is not good here since react will render the UI once with the stale data, and then run the effect, resetting the state, and then render again.
And finally coming to your example:
<>
{someCondition && <div className="foo">a</div>}
<p>b</p>
<button>some button</button>
<CustomElement myAttr="attr" />
</>
If the value of the condition changes between renders, all four of the elements will be rendered. Case 3 and recusing on children happens here.
If you don't want them to be rendered, you can tell react that the other elements are stable by setting a constant key for the elements.
<>
{someCondition && <div className="foo">a</div>}
<p key="paragraph">b</p>
<button key="button">some button</button>
<CustomElement key="custom" myAttr="attr" />
</>
The concept you are looking for is React reconciliation https://reactjs.org/docs/reconciliation.html
During rendering, react compares the old virtual tree to the new one to know when it has to mount/unmount elements. To do so, it will compare node per node (understand old node at index 0 with new node at index 0, old node at index 1 with new node at index 1, etc). If the node type matches, it will keep the component mounted (keeping its state and class component custom attributes) and might only rerender pure/memoized components if the props are updated. Else it will unmount the old component and mount the new one.
In your example:
# Nodes when someCondition=true
<div className="foo">a</div>
<p>b</p>
<button>some button</button>
<CustomElement myAttr="attr" />
# Nodes when someCondition=false
false # it is still part of the react tree!
<p>b</p>
<button>some button</button>
<CustomElement myAttr="attr" />
So react will compare node by node and only unmount div.foo
. The other node indexes are still matching. So your code is good without keys.
Then what is a scenario that could go wrong without keys?
render() {
if (someCondition) {
return <>
<p>b</p> // index 0
<button>some button</button> // index 1
<CustomElement myAttr="attr" /> // index 2
</>
} else {
return <>
<div className="foo">a</div> // new index 0
<p>b</p> // new index 1
<button>some button</button> // new index 2
<CustomElement myAttr="attr" /> // new index 3
</>
}
}
In the above example, when the condition is switched, the node types at each index are not matching anymore. So react will unmount and remount everything.
What could be even worse?
render() {
if (someCondition) {
return <>
<p>b</p> // index 0
<ComponentWithInternalState id='1'> // index 1
<ComponentWithInternalState id='2'> // index 2
</>
} else {
return <>
<ComponentWithInternalState id='1'> // index 0
<ComponentWithInternalState id='2'> // index 1
</>
}
}
Now let's suppose, that someCondition is true on first render, we will have:
- an instance of p
- an instance (c1) ComponentWithInternalState with id 1
- an instance (c2) ComponentWithInternalState with id 2 The user interacts a bit with 1 and modifies some of its internal state.
Now, someCondition becomes false, and the following will happen:
- p is unmounted (because p doesn't match ComponentWithInternalState)
- an instance (c3) of ComponentWithInternalState with id 1 is mounted (because p doesn't match ComponentWithInternalState)
- the instance c1 is reused (its internal state is kept) and its prop is updated to id=2 => this could be very bad
- c2 is unmounted (because there is no node with index 2 when someCondition is false)
Keys would solve those problems
In both cases, if keys have been used, react wouldn't have merely relied on node indexes and would have avoided mixing instances and unneeded mounting/unmounting.
But those problems should be avoided in the first place
Though, one has to note that those examples are to illustrate how dumb React reconciliation could be if not properly guided. It's actually a very bad practice to have if/else/switch returns in the render. One should normally use conditional blocks (as you did). key
should mostly be used when the user is manipulating dynamic arrays because the number as well as the order of the nodes will likely change which makes it impossible for React to know how to reconciliate properly.
Bonus Note
Last but not least, using indexes as keys {vals.map((v,index) => <CompWithInternalState key={{index}} />)}
is exactly the same as not providing any key (except that you will hide the react warning and as such expose yourself to very tricky bugs). As much as possible keys should be ids that could uniquely identify your component (userId, settingId, sectionId...)
I think this is exatly what u want.
<>
{ someCondition ? <div className="foo">a</div> : ''}
<p>b</p>
<button>some button</button>
<CustomElement myAttr="attr" />
</>
You can use if-else
short. The first if someCondition = false
we render className="foo"
else <p>, <button>, CustomElement
return (
<>
{ !someCondition ?
(<div className="foo">a</div>)
: ''
}
<p>b</p>
<button>some button</button>
<CustomElement myAttr="attr" />
</>
)
You should only use a key prop when you have a list or map through an array. In your example it is not neccesarry because the other elements like button and p stay the same. When the condition is true react will notice this and compare the "old" DOM with the Virtual DOM and only re-render what has changed. In your case the div.
Keys would solve those problems in both cases, if keys had been used, React wouldn't have relied on node indexes and would have avoided mixing instances and unneeded mounting/unmounting. However, those problems should still be avoided in the first place, though one has to note that those examples are to illustrate how dumb React reconciliation could be if not properly guided. Keys should mostly be used when the user is manipulating dynamic arrays because the number as well as the order of the nodes will likely change which makes it impossible for React to know how to reconciliate properly