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?
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
.
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} />)
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:
<div>
has 3 arrays as immediate children
- not the 9 <p>
components.<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.<p>
component has a key
inside its parent array (0-2); the only place collisions would be possible.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With