What is the importance of keys in React?

Photo by Jason D on Unsplash

What is the importance of keys in React?

React philosophy

Lets briefly understand how React works to set up some context on component rendering.

  • React uses the process of reconciliation to update the browser DOM which contributes to its efficient rendering and updating of the UI. To carry out this process React uses

    • Virtual DOM - a lightweight in-memory representation of the actual DOM

    • Diffing Algorithm - outputs the set of difference between two input

  • The current Virtual DOM is compared with the new one generated after state or prop changes using the diffing algorithm.

  • It then updates only the parts of the actual DOM that have changed, minimizing the number of DOM mutations required.

Where keys come into play

Keys play a crucial role in the rendering process, particularly when dealing with lists of JSX elements. Specifically, keys are required in the following scenarios:

  1. Rendering a list of JSX elements

  2. Adding new elements to a list

  3. Removing existing elements from a list

  4. Modifying existing elements in a list

Choosing the right key

When assigning keys to JSX elements, it's essential to choose an appropriate value to ensure optimal rendering performance. Possible key values include:

  1. none - Not providing a key itself

  2. index - Using the index of the item in the list

  3. item.id - Using a unique identifier associated with the item (such as a database ID)

  4. Generate on the fly - Combining the index and item ID or generating a unique key on the fly ( for eg. using Math.random() ).

React's behavior with different key values

Let us take an example to observe how React behaves with different key values.

Here we have four lists of fruit boxes that are rendered initially, each list with different key values being passed to each list item as mentioned before.

  • We have two global functions

    • removeItem() - removes item from the list

    • addItem() - appends new element to the new list

  • Each List component maintains its own state to keep a count of fruits in the respective box.

Demo

Now let's add fruit in each box incrementally as you go down a list ( refer to image below ). Follow the same drill for all lists.

Let's try removing a list item now

  1. Case 1: Remove a fruit box from below ( pear )

    The results are as expected and the remove item feature seems to work fine 🤩

  2. Case 2: Remove a fruit box from above ( apple

    Wait, what?!! React did go insane there! 😵
    Only the instance where we used item.id as key worked as expected, the rest of them are unexpected results.

Let's understand what happens under the hood🧐

As per the react documentation state is tied to a position in the tree.
Additionally, it is also mentioned that same component at the same position preserves state.

  • no key

    In this case, its exactly why the elements shifted upwards, but the state did not. React treats all the <List/> components as identical, so it simply re-renders the existing elements when their props change. Since the state is tied to the position in the component tree, React sees one element missing from the bottom, resulting in the removal of the state for the last element.

    By default, React uses order within the parent to differenciate between components.

    The circle represents the parent node while the diamonds represent sibling nodes ( <List/> ) and the value below represents the state of each component.

    Hope the above illustration makes it clear to understand. The state was associated with the position and since the element type ( <List/> ) is the same at the given positions, before and after, the state remained unchanged even tho the number of elements decreased.

  • key = index

    Specifying a key tells React to use the key itself as part of the position, instead of their order within the parent.

    Yet, in this case, when the first item in the list is removed, React's diff algorithm recognizes that the content has changed, but since the key value at each respective position remains the same, React assumes that the components have only been updated. As a result, it updates the name of the fruit box without physically removing the first element from the DOM.

    • Passing the index as the key value which is unique still does not yield the expected behavior ?! 🤔

The new values added inside the diamond represent the key values.

  • key = item.id

    In this case, the key value assigned to each component is not only unique but also remains the same throughout the component's lifecycle. By carefully observing the previous example, you'll notice that the key for the "orange" element was changed to 0 when the first element was removed from the list.

    However, in the current case, the key for "orange" remains unchanged even after removing the first element.

    The reason behind this is that React uses the key as a means to track the position of existing components. When the first position is removed, React recognizes not only the change in props but also the change in the key of the component. By having a consistent key value, React can effectively keep track of the positions of the components, ensuring proper rendering and handling of updates.

    • Passing the key as a unique identifier which is also consistent throughout re-renders yields expected results!! 😃

  • key = generated on fly

    Why did this case demonstrate the most unusual behavior, causing all component states to reset to 0? 🤯

    The reason is that even though the keys were unique, after removing the first element, they were again generated with new values that were not the same for any component across re-render.

    You can see in the below illustration that key values a0, o1, g2, p3 were now changed to o0, g1, p2 unlike the case where we used index as key, even though the key value associated with a component at the respective was not consistent but they did not change across re-render.

    For React a new key implies a new component thus unmounting the current component and mounting a new one with the initial state ( 0 )

    NOTE:

    - Even key={Math.random()} would demonstrate the same behavior

    - If the elements in the list do not have a unique identifier, we can transform the array by adding a new key-value pair in the element object with a unique identifier.

Keys beyond rendering

Keys aren’t just for lists!

By now we know that keys help React distinguish between two components ( especially if they are of the same type ). We can explicitly use them to reset the state or rather it would be more precise to say, remount the component.

Here's an example I have forked from the official docs of React with a few tweaks in it.

{isFancy ? <Counter addStyle={true} /> : <Counter addStyle={false} />}

In App.js if you look into the above line of code, when the state ( isFancy ) of the parent component change, conditionally the <Counter/> component is rendered with the required prop value.

On toggling the checkbox, both components are rendered conditionally yet the state of the counter component is not reset and rather persists throughout re-render.

Looking at the code it seems that the ideal behavior should be that when you toggle the checkbox since the counter with a different prop value is rendered the count should reset to 0 ( initial state ).

If we go back to our first case ( no key ) where we have referred to the react docs which say same component at the same position preserves state**,** it justifies the behavior.

In order to update the app to reset the state on toggling the checkbox we can pass specific key values to help React differentiate between both Counter components.

{isFancy ? <Counter key={1} addStyle={true} /> : <Counter key={2} addStyle={false} />}

Conclusion

We visited the React philosophy, followed by discussing possible key values and what results they produce along with strong reasoning.

Does it mean that we should always be careful about choosing the right key?

As long as the list is static and there are no operations ( add, modify, remove ) done on the list, we can choose to pass index as key.

We can summarise the following points as "key" takeaways

  • Why are keys important?

    • it lets React identify the item throughout its lifetime.
  • Rules of keys

    • keys must be unique among siblings.

    • keys must be the same throughout re-renders.

  • Additional Note

    • key is not a prop.

    • You can force a component to reset its state ( remount ) by giving it a different key.

References