Back to the book, we are going to talk about the diffing algorithm and how React decides which components to update.
When React creates Elements such as
const element = <div>Hi</div>;
it is going to create a tree of objects that represent the DOM. This tree is called the virtual DOM. The virtual DOM is a lightweight representation of the DOM. It is a tree of objects that represent the DOM nodes and their attributes.
In our example, the virtual DOM will look like this:
{
type: 'div',
props: {
children: 'Hi'
}
}
When we update the DOM, React will create a new virtual DOM tree and compare it with the previous one. This process is called diffing. React will then update the real DOM with the results of the diff.
Elements are identified by their type. If the type is different, React will unmount the old element and mount the new one. If the type is the same, React will update the props of the element.
But React does not only render HTML elements, it also renders components. In this case the type is a function. If the reference to the function is different, React will unmount the old component and mount the new one. If the function is the same, React will update the props of the component.
For example, if we have a component called Button
:
const Button = ({ children }) => <button>{children}</button>;
It will be rendered as:
{
type: Button,
props: {
children: 'Click me'
}
}
This is exactly why defining a component inside another component is an anti-pattern. If the parent component re-renders itself for any reason, the child component that is declared inside will re-mount itself on every re-render, which is terrible for performance.
const Component = () => {
const Child = () => <input />; // new reference on every re-render
return <Child />;
};
React also rely on the order of the children to determine which element to update.
In a dynamic list, if we change the order of the elements, React will unmount the old elements and mount the new ones.
const List = () => {
const [items, setItems] = useState(["a", "b", "c"]);
return (
<ul>
{items.map(item => (
<li>{item}</li>
))}
</ul>
);
};
Tree will look like this:
{
type: 'ul',
props: {
children: [
{
type: 'li',
props: {
children: 'a'
}
},
{
type: 'li',
props: {
children: 'b'
}
},
{
type: 'li',
props: {
children: 'c'
}
}
]
}
}
The issue here is dynamic lists are not always ordered, and we can’t rely on the order of the elements.
This is why we need to provide a unique key to each element in the list. React will use the key to identify the element and update it instead of unmounting it.
Why we don’t key outside arrays ?
React will not throw an error if we don’t provide a key, but it will throw an error if we provide a key outside an array.
const List = () => {
return (
<ul>
<li>item</li>
<li>item</li>
<li>item</li>
</ul>
);
};
Simply because first example is a dynamic array. React doesn’t know what you will do with this array during the next re-render: remove, add, or rearrange items. So it forces you to add the “key” as a precautionary measure, in case you’re messing with the array on the fly.
Keys can also be used to reset the state of a component. If we change the key of a component, React will unmount the old component and mount the new one.
That’s it for today. It was a long post, but I hope you learned something new.
If you have any suggestions or questions, feel free to reach out to me on Twitter or LinkedIn.
P.S. this post is inspired by the book, but it’s not a summary of it. I’m just sharing what I learned from it. If you want to learn more about React, I highly recommend you to read it.