Hey there π
In the previous article, we've discussed about the render phase and the commit phase in React, with clear explanation and visualization.
In that article, I mentioned the term Diffing
But we didn't go into How Diffing really works?
Moreover, the key
prop is a very special prop in React, it works in combination with the Diffing Algorithm.
They all are very important concept in React. Let's get back to it now.
HOW DIFFING WORKS
Diffing uses 2 fundamental Rules (assumptions)
2 Elements of DIFFERENT TYPES will produce different trees.
Elements with a Stable Key Prop STAY THE SAME across Renders
β‘οΈ This might sounds obvious, but it allows React to go from 1,000,000,000 [O(n^3)] to just 1000 [O(n)] operations per 1000 elements.
Diffing is comparing elements step-by-step between 2 renders based on their POSITION in the Tree.
1οΈβ£ FIRST, SAME POSITION BUT DIFFERENT ELEMENTS
Let's assume, at some point, our application is re-rendered.
In the Diffing process, we found that an element has changed at a certain position in the Tree.
Example with Different DOM ELEMENTS. π»
Before
<div>
<SearchInput />
</div>
<main>...</main>
After
<header>
<SearchInput />
</header>
<main>...</main>
Here we have a Different DOM Element, it changed from div
to header
β In this case, React assumes ENTIRE sub-tree is NO LONGER VALID.
β© Therefore, old Components (div
and SearchInput
from the Before Example) are destroyed and removed from the DOM, including their state.
They will be rebuilt as a header
with a brand-newSearchInput
component instance as a child.
β The tree might be rebuilt even if children stayed the same (and the state is reset).
Example With Different React Element (Component Instance) everything is exactly the same.
Before
<div>
<SearchInput />
</div>
<main>...</main>
After
<div>
<UserProfile />
</div>
<main>...</main>
Here the SearchInput
component changed into UserProfile
component.
So the SearchInput
component is again completely destroyed and removed (including its state), from the DOM.
2οΈβ£ SECOND, SAME POSITION WITH SAME ELEMENT
β This one is WAY more straightforward than the previous one.
π The element will be kept, as well as its child elements, including state.
π New props, and attributes are passed if they change between renders.
π Sometimes this is NOT what we want π΅ Then we can use thekey
prop.
Example with DOM Element
Before
<div className="hidden">
<SearchInput />
</div>
<main>...</main>
After
<div className="block">
<SearchInput />
</div>
<main>...</main>
Example with React Element
Before
<div>
<SearchInput value={0} />
</div>
<main>...</main>
After
<div>
<SearchInput value={2024} />
</div>
<main>...</main>
In the DOM Element example, we just changed the className
attribute.
With the React Element example, we changed the props value
from 0
to 2024
With both DOM Element and React Element example.
We all have the SAME ELEMENT in the SAME POSITION.
Thereby, the element and all of its child will be kept, state of the component will be reserved!
This state-reserving behavior is NOT what we want all the time.
In some cases, even with the same position, and the same Element between renders, we want the state to be reset.
That's when thekey
prop comes into play!
The KEY Prop π π΅
π Special Prop that is used to TELL the Diffing Algorithm that an element is UNIQUE.
π Help React to distinguish between multiple component instances of the Same component type.
π When a key stays the SAME across RENDERS, the element will be kept in the DOM (even if the position in the tree changes)
β‘ That's why we should use keys in lists.
π When a key CHANGES between RENDERS, the Element will be destroyed and a new one will be created (even if the position in the tree STAY THE SAME AS BEFORE)
β‘ It's GREAT to use keys to RESET STATE
1οΈβ£ Use keys in Lists [ Stable Key ]
No Keys Example
<ul>
<CartItem item={cart[1]} />
<CartItem item={cart[2]} />
</ul>
Adding new list item π
<ul>
<CartItem item={cart[0]} /> ππ» NEW CART ITEM
--------------------------------
<CartItem item={cart[1]} />
<CartItem item={cart[2]} />
---------------------------------- // π THESE ELEMENTS STAY THE SAME
BUT IN DIFFERENT POSITION!! π
</ul>
By adding new CartItem
to the front of the list, we shift the elements from the previous render down.
π¨ We have Same elements, but different position in the tree. So they are removed and recreated in the DOM. It's BAD for PERFORMANCE.
Because removing and rebuilding the SAME element is just WASTED work.
BUT the problem is React doesn't know that it's wasted work. Of course, we developers know that these 2 elements with item props cart[1] and cart[2]
stay the same as before. React has no way of knowing that!
That's where key
prop come into the play!
With KEYS
<ul>
<CartItem key='i1' item={cart[1]} />
<CartItem key='i2' item={cart[2]} />
</ul>
The key
allows React to uniquely identify an element. We give React the information that it does not have on its own.
Now let's add new item to the top of this list one more time!
<ul>
<CartItem key='i0' item={cart[0]} /> ππ» NEW CART ITEM
--------------------------------
<CartItem key='i1' item={cart[1]} />
<CartItem key='i2' item={cart[2]} />
---------------------------------- // π THESE ELEMENTS STAY THE SAME
BUT IN DIFFERENT POSITION, BUT now they have STABLE KEY π
</ul>
Now they have the key
prop that stay the same across render (stable key).
That's i1 and i2
in this case.
These 2 elements will still be KEPT in this case. Even though their POSITION in the tree is DIFFERENT from previous render.
They will not be removed or destroyed.
The outcome will be a bit more of a Performance UI. Of course we cannot notice the different here in a small list. But imagine it will be a tremendous improvement on a list of 1,000 or more elements.
π Conclusion, we should ALWAYS use keys in lists.
2οΈβ£ USE KEY TO RESET STATE [ CHANGING KEY ]
For better illustrate, take a look at the below Example
<QuizBox>
<Quiz
content={{
title: "Vite vs Webpack",
body: "Why should we use Vite rather than Webpack"
}}
/>
</QuizBox>
function Quiz({ content }) {
const {title, body} = content;
const [answer, setAnswer] = useState('');
return (
<div>
<h3>{title} </h3>
<p>{content}</p>
<input
placeholder="type your answer here"
value={answer}
onChange={e => setAnswer(e.target.value)}
/>
</div>
)
}
Inside each Quiz
component, we have the answer
state, which should be own and manage just in the context of one quiz.
For instance, at the given moment, the user is typing their answer to the Input.
Something like I choose Vite because it's faster and easy to use than Webpack
So now is the interesting part.
We have NEW QUIZ in the SAME POSITION
<QuizBox>
<Quiz
content={{
title: 'Best Book Ever π',
body: 'Tell me your best book title'
}}
/>
</QuizBox>
As we discussed previously, if we have the SAME element at the SAME position in the tree, the DOM Element and its state will be KEPT.
In this case, we have same <Quiz/>
Component, with different content
props.
The Quiz will be rendered with different content
props, but the answer
state still be preserved.
As a consequence, even though we have NEW QUIZ, in the answer input we still see the previous state I choose Vite because it's faster and easy to use than Webpack
That's NOT what we want! π
We want the answer
state attached to each Quiz
instance to be unique, and reset every time.
π That's where the SECOND BIG USE CASE OF key
prop come into play again!
<QuizBox>
<Quiz
content={{
title: "Vite vs Webpack",
body: "Why should we use Vite rather than Webpack"
}}
key="quiz-20" β
/>
</QuizBox>
//////// NEW QUESTION IN THE SAME POSITION
<QuizBox>
<Quiz
content={{
title: 'Best Book Ever π',
body: 'Tell me your best book title'
}}
key="quiz-21" β
/>
</QuizBox>
Here we use a unique key to tell React that this should be a DIFFERENT COMPONENT INSTANCE.
So it should create a brand-new DOM Element Each time the key
change. The result of doing this will be the answer
state will be reset back to ''
, which is EXACTLY what we need in this situation!
π Whenever you find yourself in the position that you need to reset state, just make sure you give the Component Instance a key.
Thanks for reading.
Have a great day. Bye π
Howard from Web Dev Distilled.
Reference resources:**Jonas Schmedtmann & React Documentation.