keys being modified in undocumented ways


(Acthp) #1

With HOCs (react 15), I often have a list of children from some component, with a key on each child, and map over them. The keys needs to be transferred to the new children. This happens automatically using cloneElement, though I can also manually set them if I’m, say, wrapping the unmodified child in a new parent.

The result is unexpected, as instead of (for example) key ‘mykey’, at run-time it looks like ".$.$.$.$mykey’. Or “.$.$.$mykey/.$.$.$.$mykey”. I’m not adding those “.$” or joining them with ‘/’, and I can’t find any docs explaining where this transform is coming from.

It’s problematic, because they often change in ways I don’t know how to control, resulting in burning a lot of cpu on unmount/mount of components. E.g. a key might change from “.$.$.$.$mykey” to “.$.$.$mykey”, causing it to unmount.


(Acthp) #2

Replying to my own question, after far too much time in the react code.

Important safety tip:

In react, x is not equivalent to React.Children.map(x, identity)

This is kinda horrible. The issue seems to be that the API allows you to build up children incrementally, e.g. like

  <div>
         {children}
          <p>more</p>
          <p>children</p>
  </div>

which gets represented as nested arrays, like [children, [<p>more</p>, <p>children</p>]]

Semantically this renders like the flattened array, as one would expect. But in, I think, an attempt to make it easier to combine lists from different sources, react only requires keys to be unique within their original list. So if you flatten the tree the keys have to be rewritten to preserve uniqueness. There’s a very unclear note about this in the docs.

In my case, the problematic code looked like

return condition ? expensiveMap(children) : children;

and when condition changed value, the keys on the children would all change. The solution is to instead

return condition ? expensiveMap(children) : React.Children.map(children, identity)

so in both cases a map is being done. The map of identity looks like it should be a noop, but it’s not.