Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No key collision when returning identical arrays?

I recently posted an answer to a question when I made an observation about key's when returned from an array.

I already know that keys must be unique among sibling components. This is stated in the official React documentation, here:

Keys used within arrays should be unique among their siblings. However they don't need to be globally unique. We can use the same keys when we produce two different arrays.


However, in contrast to the bold text above, it seems like React is fine with same keys for identical arrays. How come the following snippet works and the keys don't collide?

class MyApp extends React.Component {

  renderArray = () => {
    return [
      <p key={0}>Foo</p>,
      <p key={1}>Bar</p>,
      <p key={2}>Baz</p>
    ];
  }

  render() {
    return (
      <div>
        {this.renderArray()}
        {this.renderArray()}
        {this.renderArray()}
      </div>
    );
  }
}

ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Obviously, this doesn't work:

class MyApp extends React.Component {

  render() {
    return (
      <div>
        <p key={0}>Foo</p>
        <p key={1}>Bar</p>
        <p key={2}>Baz</p>
        <p key={0}>Foo</p>
        <p key={1}>Bar</p>
        <p key={2}>Baz</p>
        <p key={0}>Foo</p>
        <p key={1}>Bar</p>
        <p key={2}>Baz</p>
      </div>
    );
  }
}

ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

So my question is, why does the first snippet work but not the second? It seems like they should both yield the exact same result?

like image 958
Chris Avatar asked Jun 22 '17 09:06

Chris


2 Answers

Answer lays in how the createHierarchyRenderer method/function works. Source createHierarchyRenderer.js

It is reducingRight and appending Components by render to the conspect concating the result.

It creates renderHierarchy which is 2d array of components, so... Your renderArray invocations have different indexes in hierarchy which makes their hierarchies independant of eachother.

Second case is not working, because key's are pseudohierarchy to boost performance - you can do this without keying, but - it is very bad practice for bigger render queue. So, passing key - overwrites the dimension represented by key.

Bonus:

Why it works without passing keys? Which keys are the best? How to improve?!

Source:

/**
 * Generate a key string that identifies a component within a set.
 *
 * @param {*} component A component that could contain a manual key.
 * @param {number} index Index that is used if a manual key is not provided.
 * @return {string}
 */
function getComponentKey(component, index) {
  // Do some typechecking here since we call this blindly. We want to ensure
  // that we don't block potential future ES APIs.
  if (component && typeof component === 'object' && component.key != null) {
    // Explicit key
    return KeyEscapeUtils.escape(component.key);
  }
  // Implicit key determined by the index in the set
  return index.toString(36);
}

This method is called by traverseStackChildrenImpl method in the same file. If you will dig into these magic:

for (var i = 0; i < children.length; i++) {
  child = children[i];
  nextName = nextNamePrefix + getComponentKey(child, i);
  subtreeCount += traverseStackChildrenImpl(
    child,
    nextName,
    callback,
    traverseContext,
  );
}

You will get why it is working, and you will know why the keying works without keys, and where you suffer performance looses. It is nice to pass key but... React can automagically pass the index for us, key is designed to take the id of component creator object... For example:

users.map((v, i) => (<UserItem data={v} key={i} />)

Makes no difference than not passing the key, but it is considered a bad practice:

users.map((v, i) => (<UserItem data={v} />)

key magic is designed to take id of user to not rerender rest of users in case of appending array inside fe. users = users.unshift(someuser). The best available setup is:

users.map((v, i) => (<UserItem data={v} key={v.userId} />)
like image 80
Daniel Mizerski Avatar answered Sep 23 '22 03:09

Daniel Mizerski


After considering the valuable input from the other answers posted here, I dug a bit deeper and re-created the two snippets in my own application, and then inspected the Component with the React Developer Tool.

I found the following:


First snippet:

In the first snippet where the renderArray() function is used, the children of the parent <div> does not have 9 <p> tags as children but rather 3 arrays, each containing 3 <p> tags.

This isn't so apparent at first, because the rendered output in the dev-tools (left-hand part), suggests 9 children, not 3.

Here I have expanded the first, fourth and seventh item, which are the items which I have given key as 0.


Second snippet:

In the second snippet where all <p> tags are placed directly as children of the <div> we get a similar output (if we ignore items 4-9 not showing, due to key collision), but the dev-tools show that the <div> has the expected 9 children. Hence the collision.

As with the previous snippet, I have expanded the first and fourth item where the key is 0. (forgot the seventh).


DL;DR

To summarize, when a React Component is given an array as children, the contents of that array do not get unpacked/destructed and given as children. Instead, the array remains as children and serves as a transparent container for its contents.

As such, there is no key collision because:

  • The parent <div> has 3 arrays as immediate children - not the 9 <p> components.
  • Each <p> component is "locked" within its parent array, because that's where it's defined. It obviously cannot suddenly move into one of the other arrays.
  • Each <p> component has a key inside its parent array (0-2); the only place collisions would be possible.
like image 33
Chris Avatar answered Sep 26 '22 03:09

Chris