ReactJS - Passing an element of an array of Objects as props - javascript

I am building aReact Application.I have a state array which holds array of blogs.
I am rendering each blog using a BlogDisplay component.
Following is my code :
render() {
for( var i = 0;i < this.state.blogData.length;i++){
blogDataToRender.push(<BlogDisplay blogData = {this.state.blogData[i]} onUserTriggerForDetailedView ={this.props.onUserTriggerForDetailedView}/>);
}
return (
<div>
{blogDataToRender}
</div>
);
}
I am getting a warning like this :
index.js:5279 Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `BloggerHome`. See https://facebook.github.io/react/docs/lists-and-keys.html#keys for more information.
in BlogDisplay (created by BloggerHome)
in BloggerHome (created by Blogger)
in div (created by Blogger)
in div (created by Blogger)
in Blogger
Is my modelling correct ? How can I correct this?
Thanks a lot.

You need to add a key for each BlogDisplay component:
for( var i = 0;i < this.state.blogData.length;i++){
blogDataToRender.push(<BlogDisplay key={i} blogData = {this.state.blogData[i]} onUserTriggerForDetailedView ={this.props.onUserTriggerForDetailedView}/>);
}
The key is important for child components in an array. Because when one of those child components is under operation, React needs the key to determine which one is operated. Also, when the source array data is changed, React needs the key to check the diff of components.
For in-depth explanation about why keys are necessary, please refer to the official document.
What should be the value of key for each child component?
According to the above document:
The key only has to be unique among its siblings, not globally unique.
Also, blogData[i].id is the best choice if there is a unique id in the source data. Indexes as in the above example is not recommended, if the child components would reorder (performance issue).

Each element in React needs to have a unique key, which React uses to identify it. When you just render an element overtly, React automatically assigns one. However, when you assign an ARRAY of elements, each element in that array isn't assigned a key by default, so you need to. Something like this:
blogDataToRender.push(<BlogDisplay key={i} blogData={this.state.blogData[i]} onUserTriggerForDetailedView={this.props.onUserTriggerForDetailedView}/>);

Check out Chris' reply on Understanding unique keys for array children in React.js.
To fix this, you must set a unique id for each of your elements and then add a key prop to each element. You can read more about how React uses keys to identify elements in lists in the official documentation.
This is how your render() method should look:
render() {
return (
<div>
{this.state.blogData.map((item) => {
return (<BlogDisplay key={item.id} blogData={item} onUserTriggerForDetailedView={this.props.onUserTriggerForDetailedView} />)
})}
</div>
);
}

To answer your question quickly all you need to do is read the error and fix it! Add a unique key value to each component like so:
...
for( var i = 0;i < this.state.blogData.length;i++){
blogDataToRender.push(
<BlogDisplay
key={this.state.blogData[i].id}
blogData={this.state.blogData[i]}
onUserTriggerForDetailedView{this.props.onUserTriggerForDetailedView}
/>
);
}
...
In this case I assume that each one of your blog entries has a unique id, the thing you need to be aware of here is that the key value needs to be unique and not depending on the position of that element inside the array. Every time you render these elements that key needs to be the same, this value is used by React to optimize re rendering and avoid rendering items that did not change. If you want to understand why this is necessary here is a good link. Next time I recommend reading the docs because they are very well done and you can learn a lot from it.
Now the way I would do it is using map:
...
const blogDataToRender = this.state.blogData.map(blogData =>
<BlogDisplay
key={blogData.id}
blogData={blogData}
onUserTriggerForDetailedView{this.props.onUserTriggerForDetailedView}
/>
);
...
This helps readability and leverages an iterator so you don't need to keep track of the index. It is a widely used pattern when developing in React.

Related

ReactJS: How to add a new element in array state and to preserve "prevState" for them?

My array state is rendering and i can to push new elements without problem, but, when i trying add in specific index, react is changing the state of the previous elements.
Example of the previous state:
{this.state.numOfRows.map((v, k) => (
<Item add={this.add.bind(this)} key={`item-${k}`} index={k} item={v} />
))}
This will render a html element with option = 0;
So, when i call this.add(index) prop i can push 2 or 3 more elements on my array and i change options for 1 and 2.
But, when i call this.add(index) to add a new element in index 2, for example, then the previous options that i selected are updated and the new element with 0 in option is displayed in the last index.
enter image description here
When rendering lists in React, you should specify a key that is based on the unique identity of the item being rendered, rather than the (current) index/position of that item in the source array.
In your case, if each items in the numOfRows array had a unique identifier (ie someUniqueId as shown below) then this would be a better candidate for use with the key prop of each <Item /> that is rendered:
{
this.state.numOfRows.map((v, k) =>
(<Item add={this.add.bind(this)}
key={`item-${ v.someUniqueId }`}
index={k}
item={v} />))
}
The reason for this is that React determines if an item in a rendered list needs to be updated based on that items key. Rendering anomalies will arise when an item's key (based on it's index) remains the same despite the actual data for that item changing.
For more information on this, see React's Lists and Keys documentation.
Also, see this in-depth article on the key prop and specifically, this summary of when an index based key prop is okay.

React index key is not working in the map

I have used index key but it's still showing errors. Also tried with unique id from JSON data but can't solve this.
ERROR
Warning: Each child in a list should have a unique "key" prop.
const faqdata = this.props.faqData.map((faq, index) => (
<li key={index}>
{faq.description}
</li>
));
Warning: Each child in a list should have a unique "key" prop.
You will get this warning only when two or more items in the list have been supplied with the same key, Your code looks fine for me, it shouldn't be giving this warning, please try with a console.log(index) inside the map.
const faqdata = this.props.faqData.map((faq, index) => {
console.log(index);
<li key={index}>
{faq.description}
</li>
});
Anti Pattern:
You should not be using index of the map for you Key, its an antipattern,
it may lead to unpredictable results.
Please check this https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
You should be using proper unique-id for your Key, like a DB id or some unique-id.
This Key is used internally to identify the DOM elements to render and not to render again.
In retrospect to other answers quoting that you should not use array index as a key, as much as it is an antipattern, it will still resolve the warning.
What you're doing is right and should remove the warning. Although I suspect the warning is coming when you iterate again over the result of your faqdata.
Every time you render something from an array, each entry should have the key attribute.
You must use ID, non duplicate name (or whatever unique you call it) but its index, because it compares the VDOM by its key:value pairs

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?
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} />)
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.
React keys must be unique for each element inside an instance of a parent. In your case, you have 3 instances of {this.renderArray()}, each with its own inner id. So children inside each instance have unique keys, but they are not siblings with children form other instances.
See this example from the official documentation: https://facebook.github.io/react/docs/lists-and-keys.html#keys-must-only-be-unique-among-siblings

React cloneElement not setting key

I'm building a table control which dynamically generates keys (which I understand may not be a good idea - I imagine the key should be uniquely associated with the data it represents otherwise React could just generate unique ids for us?), but either way it seems the keys are not being set and I have no idea why. The rows in the table are generated with a function that can be found here. Basically I have a helper component which takes an optional component to transform all child elements - here is one of those transform functions (from the TableBody component):
const transformRows = (keyPrefix) => (children, passthroughProps) => (React.Children.map(children, (c, i) => {
return React.cloneElement(c, {
key: `${keyPrefix}-${i}`,
style: rowStyle,
className: rowClassName,
columnDefinitions: columnDefinitions,
rowData: tableData[i],
includeVerticalScrollbar,
...passthroughProps
});
}));
the weird thing is that when I step through the code it seems the key is being assigned, but then I get a warning in the browser Each child in an array or iterator should have a unique "key" prop which traces back to here in the stack trace.
Any ideas?
One way to overcome this is to wrap each cloned element with React.Fragment and set the key on the latter.
Like so:
<>
{connections.map(connection => (
<React.Fragment key={connection.id}>
{React.cloneElement(element, connection)}
</React.Fragment>
))}
</>
from the docs React.cloneElement
Unlike React.addons.cloneWithProps, key and ref from the original
element will be preserved.
So if you set key for Cell, than each clone get key automaticaly.

Child array unique key in ReactJS using a non-child component

So I understand the unique key: if I have something like:
array.push(<ChildComponent key=some_key />
But if I have something like this:
cols.push(<td key={i}>Some value</td>)>
(While I am creating a table component that has pagination...). I am not sure about that key... That table supports sorting (for columns) so... I believe when we sort, if we use that "i" key, something will be broken. Is in this case, a key necessary? Or it is only necessary in an array that contains child components. In any case, if we don't use a key we get a warning from React that we want to avoid/fix.
Index based keys are sufficient unless:
items are inserted at or removed from anywhere except the end
the order of items in the items can change
items can be replaced with other items
If any of those are true, you should use a key which identifies the items themselves, rather than their position in the array. Otherwise the index sufficiently identifies it.
Using keys correctly has performance improvements when the subtrees are large or involve img/video/other expensive components. It's also a requirement if you want to animate items being added/removed/reordered correctly.
So if I understand you correctly, "i" is some index of the sorted array? If so, that will break down because components should be keyed to something unique to their data (and thus their rendering), not something that is varient like an index.
Because the key isn't consistently tied to the same piece of data, this will simply cause the div's to rerender their contents, as opposed to simply rearranging their order. Try setting key to a hash based on their data!
If you're looking for a nice solution to generating unique keys, I plucked this one from some of the React/Flux examples and it's worked great for my team and I.
uuid: function () {
var i, random;
var uuid = '';
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-';
}
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16);
}
return uuid;
}
Keys are about state. By state I mean not justthis.statebut also anything you store directly inthisand need or want to preserve between renders. If your children do not have state, keys do not matter. You could generate a random key every time for each child and that would work. But if some children have state, that approach will create a new child, initialized to its initial state. So if you need to preserve child state between renders the child key cannot change. This will ensure that the child component is preserved rather than recreated.
See Child Reconciliation here: http://facebook.github.io/react/docs/multiple-components.html

Categories