Search code examples
reactjsreconciliation

Why can't React use a CONTENT to automatically generate keys?


That's obvious that for the diffing algorithm in React keys are essential. But I was wandering, why React just can't automatically generate keys based on the content we iterate over?

I also assume that items can share some similarity, or cab be identical in terms of content, but isn't it possible to generate keys once user open a page and somehow attach them to the items, so it is stable?

Or maybe there where attempts to solve the problem, if so, I would be grateful if you share it to me.

Update

Thank you guys for your answers, I've learnt a lot! Also a thing I had in mind: what we developers do when there is no stable id (e.g. user added an item which is not yet saved into DB). In the cases we just generate id, and attach it to the object, or element in an array, but we do not generate ids on a fly, so it remains stable over time.

What if React just generate ids for all arrays which are involved into rendering process, in other words, arrays which are directly used in render function?

It can be done only once, during phase Commit phase, or whatever. Also I believe, the id can be readonly, or something, so user can't erase the id.

p.s.s While I was writing p.s. question above, I realized, autogenerating id for arrays wouldn't work, since I've missed two things. All side effect react can do only during the Commit phase, but not Render phase. But that's not the main problem.

The main problem is when we use filtering or sorting on a back-end side. Since we receive a new array, filtered one, we would need to regenerate ids for those elements, but basically, that's the same html elements, in which we can change content to match filtering order. That's the same as Slava Knyazev mentioned.


Solution

  • React can't generate keys, because the entire point of keys is for you to help React track elements during it's tree-diffing stage.

    For example, lets say you have the following code, where you naively use content instead of identifiers for your keys:

    const people = usePeople(); // [{ id: "1", name: "James"}, {id: "2", name: "William"}]
    return <ul>{people.map(p => <li key={p.name}>{p.name}</li>}</ul>
    

    The above code will function and behave as you would expect. But what happens if the name of a person changes? To understand it, lets look at the tree it generates:

    ul
      li(James) James
      li(William) William
    

    If James becomes Josh between renders, the new tree will look like this:

    ul
      li(Josh) Josh
      li(William) William
    

    React will compare the two results and conclude the following:

    • li(James) is to be removed
    • li(Josh) is to be added

    However, if we set our key prop to p.id, then the old and new tree will look as follows, respectively:

    ul
      li(1) James
      li(2) William
    
    ul
      li(1) Josh
      li(2) William
    

    And when React compares the two, it will identify that James has become Josh, and needs only the text adjusted.

    In the first scenario, the <li> component is completely destroyed, and a completely new component takes its place. Both of these actions run a complete React lifecycle for the component. In the second, it remains untouched, and only the text inside changes.

    While in this contrived scenario, the performance penalty in the first case in minimal, it may be very significant with complex components.