Sunday, 4 February, 2024 UTC


Summary

The bugs generated by using the index of the map function as keys in React can be quite hard to identify and frustrating. Let's see how we can avoid them!
The key property uniquely identifies an element from a list. React uses it to check if the element is new or must be updated when some state changes in the app.
Even more, React relies on the key property to find the shortest path to update the DOM.
While in development mode React even gives a warning in the console if we try to list some items, but don't provide the key property:
Warning: Each child in a list should have a unique "key" prop.
Let's take this very simple app:
const App = ()=> {
    const [fruits, setFruits] = useState([
        {id:12, description: '🍎 apple'},
        {id:34, description: 'πŸ‹ lemon'}
    ])

    const addPears = ()=> {
        const newFruits = [...fruits]
        newFruits.unshift({id:56, description: '🍐 pear'})
        setFruits(newFruits)
    }

    return (<form>
        <fieldset>
            <legend>❌ Using indexes as keys</legend>
            {fruits.map((f, i)=> (<p key={i}>
                {f.description} <input />
            </p>))}
            <button type="button" onClick={addPears}>
                Add pears
            </button>
        </fieldset>
    </form>)
}
If you run the app inside the browser, you will not see any problems. Clicking the add button inserts a new item at the top of the list. All works as expected.
So far, using indexes as keys may seem ok.
Why using indexes as keys in React is a bad idea and leads to bugs?
Let’s now try an experiment. Let's take the initial app and type some text in each input associated with a list item:
If we now click the add button, we will see that the text written in the text inputs will not be synchronized with the items list.
React ads a bug in the UI by swaping the text of the two existing elements, and inserts the last item at the end of the list.
The bug would not have happened if we had used something such as the id of the item as the key value:
<legend>πŸ‘ Using unique ids as keys</legend>
{fruits.map( f=> (<p key={f.id}>
    {f.description} <input />
</p>))}
The reason why React swaps the text of the two existing elements, and inserts the last item at the bottom as if it were new, is because we are using the index of the map function as the key.
In fact, the index always starts from 0, even if we push a new item to the top of the list, so React thinks that we changed the values of the existing two and added a new element at index 2. Is the same behavior as if would have been without using the key property at all.
Therefore, make a rule to never use indexes as keys when rendering lists in React!
You can check out the full code on my Gitub and play with the live example here.