Understand Diffing Algorithm and the Key Prop

HowardHoward
7 min read

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)

  1. 2 Elements of DIFFERENT TYPES will produce different trees.

  2. 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-newSearchInputcomponent 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 thekeyprop.

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 thekeyprop 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.

6
Subscribe to my newsletter

Read articles from Howard directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Howard
Howard

πŸ™‹β€β™‚οΈ I'm a Software Engineer, Voracious Reader, Writer (tech, productivity, mindset), LOVE Fitness, Piano, Running.πŸ’» Started coding professionally in 2020 as a full-time Frontend engineer. βš—οΈ I make things in the messy world of JS, Computer Science, and Software Engineering more digestible. Or I would like to say β€œDistilled” πŸ“Ή Documenting my learning journey, and working experience along the way. Share how I learn and build my personal projects.