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.
Related
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.
I am creating input fields dynamically based on the number of object in my state array. Beside each field I am adding a button to remove that field. However, when the button is clicked it behaves in an unexpected way.
Below is the visual demonstration:
When I press "Remove Option" button on "Option 0":
The output is like :
However, when I see from console.log() the correct object is being removed. These are console.log() outputs before:
and after the above button click:
Here is how I loop from the array in my render():
const questions = this.state.values_array.map((question, index) => {
return (
<div key = {question.question_id}>
{this.state.options_array.map((option, i) => (
option.questionID === question.question_id ? //to show only this question's options
<div>
<span>Option {i}:</span>
<TextField type="text" defaultValue={option.description} />
<span>Value:</span>
<TextField type="number" defaultValue={option.value}/>
<button onClick={() => this.removeOption(i)}>Remove Option</button>
</div>
:
null
))}
</div>
)
}
Here is my removeOption() method I am using to remove the input fields:
removeOption(index){
let options = [...this.state.options_array];
options.splice(index, 1);
this.setState({ options_array: options });
}
And here is how I am calling it in my render's return:
return (
<div>{questions}</div>
)
The flaw of this approach is that in JavaScript, objects and arrays are reference types, so when we get an array, we actually get a pointer to the original array's object managed by react. If we then splice it, we already mutate the original data and whilst it does work without throwing an error, this is not really how we should do it, this can lead to unpredictable apps and is definitely a bad practice. A good practice is to create a copy of the array before manipulating it and a simple way of doing this is by calling the slice method. Slice without arguments simply copies the full array and returns a new one which is then stored. And we can now safely edit this new one and then update to react state with our new array. let me give you and example:
We have an array like this const arr=[1,2,3,4,5]. This is original array.
As I told you before, we can do that like this:
const newVar=arr.slice();
newVar.splice(Index,1);
console.log(newVar);
Or
An alternative to this approach would be to use it a ES6 feature, it is the Spread Operator
Our prior code can be something like this:
const newVar=[...arr]
newVar.splice(Index,1);
console.log(newVar);
That's it. Good luck
You are missing the keys for the div containers. React needs to know which DOM Element has been removed so it re-renders it. Also, do not use the index of map as the key, instead use something like the id e.g. option.questionID.
you need to filter out the individual item from the list
removeOption(index) {
const options = this.state.options_array.filter((items, itemIndex) => itemIndex
!== index)
this.setState({ options_array: options });}
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
I'm having some trouble while learning ReactJs. I keep having this warning :
Warning: Each child in an array or iterator should have a unique "key" prop.
What loses me is that I did gave a unique key to each of my elements, in that piece of code :
let tab = props.todos.map((t) => {return <TodoListItem key={t.id} todo={t} onRemove={props.onRemove} /> })
My TodoListItems are <li> elements, I also tried to give them a key then I tested the existence and the value of {t.id} before giving it as a key and it's ok. I also cleared the cache before every execution.
I'm out of solutions, that's why I'm asking for your help.
Thank you in advance.
The Safest way to enforce a unique key is to use the index parameter of your map function.
let tab = props.todos.map((t, index) => {return <TodoListItem key={index} todo={t} onRemove={props.onRemove} /> })
If you truly want to use your t.id as the key, I would suggest temporarily changing your code to use the index (per above snippet), but dumping out your t.id in your rendered markup to validate that your id's are, in fact, unique.
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.