React Reconciliation & Diffing

·

5 min read

All of us are aware of the fact that React uses Virtual DOM to make the real DOM of our React app but have you ever thought about how the Virtual DOM detects what changes need to be done and what not to? So, let us discuss that.

Reconcillation

Reconciliation is the process by which React compares the tree present in Virtual DOM and determines which parts need to be changed and only repaints them into real DOM.

Diffing

Diffing is the algorithm based upon which React compares the changes that need to be done. Diffing calculates changes based on two things:-

  • If the DOM element type changes then the new tree is built from the parent of the element changed but if the element type is the same then only the element gets rerendered and not its parent.
  • A key should be provided among siblings of the same type of component elements to differentiate among them, to let React know which element is changed, and to only rerender only that element and keep others as it is.

So let us take a few examples, and understand how those changes are calculated

Changes based on DOM element type

const App = () => {
    const [ change, setChange ] = useState(false);

    return (
       <div className="App">
           {change ? <div>Div Element</div> : <p>Paragraph Element</p>}
           <button onClick={() => setChange((prev) => !prev)}>Change</button>
       </div>
    )
}

So, in the above example, we are toggling the state on button click and when state is true then <div> element shows up and when it is false then <p> element. Let us see how the DOM is getting changed on toggling state.

first-ex.gif

So, as we can see that when the state changes then the element as well as their parent div gets rerendered, why is it so? It is because on toggling state we are changing the element type itself and when the type of element changes then according to diffing algorithm React destroys the old tree and makes the new tree from the parent of the element changed.

Now, let us see what happens if we have the same element type.

const App = () => {
    const [ change, setChange ] = useState(false);

    return (
       <div className="App">
           {change ? <p>Paragraph One</p> : <p>Paragraph Two</p>}
           <button onClick={() => setChange((prev) => !prev)}>Change</button>
       </div>
    )
}

second-ex.gif

In the above example, only the element is getting rerendered and not its parent, it is because both of the elements are of <p> type and when changed elements are of the same type then only elements get rerendered and not its parent.

There is one more follow-up example of this, where only content inside the element gets rerendered and the element remains the same.

const App = () => {
    const [ count, setCount ] = useState(0);

    return (
       <div className="App">
           <p>Count is: {count}</p>
           <button onClick={() => setCount((prev) => prev + 1)}>Increase</button>
       </div>
    )
}

third-ex.gif

When only the content inside the element changes, then React only rerenders the content of the element and not the element itself.

Changes based on DOM elements of the same type

const App = () => {
    const [items, setItems] = useState(["item1", "item2", "item3"]);

    return (
       <div className="App">
           {items.map((item) => (
              <li>{item}</li>
           ))}
          <button onClick={() => setItems((prev) => [...prev, "item4"])}>
               Add
          </button>
       </div>
    )
}

fourth-ex.gif

In the above example, on click of a button we are adding a new item at the end of the list so, only the last element added gets rerendered.

Now let's see what would happen if we add a new item at the start of the list.

const App = () => {
    const [items, setItems] = useState(["item1", "item2", "item3"]);

    return (
       <div className="App">
           {items.map((item) => (
              <li>{item}</li>
           ))}
          <button onClick={() => setItems((prev) => ["item4", ...prev])}>
               Add
          </button>
       </div>
    )
}

fifth-ex.gif

So, when we add an element to the start of the list, then all the siblings of that element get rerendered it is because React is not able to know which element is changed and rerenders all the siblings of that element.

We can counter this problem by providing a unique key value to every <li> when it is mapped. React provides us with a key attribute that helps React distinguish between every sibling element. A key should be unique only among siblings, not the whole document itself.

const App = () => {
    const [items, setItems] = useState(["item1", "item2", "item3"]);

    return (
       <div className="App">
           {items.map((item,index) => (
              <li key={index}>{item}</li>
           ))}
          <button onClick={() => setItems((prev) => ["item4", ...prev])}>
               Add
          </button>
       </div>
    )
}

sixth-ex.gif

So, in the above example, we have provided the key but still, all the elements get rerendered, why is it so?

It is because we have provided the index of those items as the key and when we add a new element to the start of the list then the index of other elements also gets changed.

So, it is a good practice to give a unique id to every element which can be used as a key for that element while it gets rerendered.

const App = () => {
    const [items, setItems] = useState([
       { id: 1, name: "item1" },
       { id: 2, name: "item2" },
       { id: 3, name: "item3" }
    ]);

    return (
       <div className="App">
           {items.map(({ id, name }) => (
              <li key={id}>{name}</li>
           ))}
          <button onClick={() => setItems((prev) => [{ id: 4, name: "item4" }, ...prev])}>
               Add
          </button>
       </div>
    )
}

seven-ex.gif

In the above example, we gave a unique id to every item which is used as a key, so React is able to differentiate between every element and then only rerenders the element which is changed.

Conclusion

  • So, when the element type changes then the new tree is built from the parent of the element changes and that gets rerendered.
  • But if the element is of the same type, then only that element gets rerendered, and the rest all remain the same.
  • We need to pass a key value that is unique to that element among its siblings, so React is able to know which sibling has been changed and only rerenders it.

I hope this blog, made you understand Diffing and Reconciliation in much depth, you can read more about it here.