Problem
I'm getting this warning:
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of EventsTable. See fb.me/react-warning-keys for more information.
react-runtime-dev.js?8fefd85d334323f8baa58410bac59b2a7f426ea7:21998 Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of Event. See fb.me/react-warning-keys for more information.
Source
This is EventsTable:
EventsTable = React.createClass({
displayName: 'EventsTable',
render() {
console.dir(this.props.list);
return (
<table className="events-table">
<thead>
<tr>
{_.keys(this.props.list[0]).map(function (key) {
if (key !== 'attributes') {
return <th>{key}</th>;
}
})}
</tr>
</thead>
<tbody>
{this.props.list.map(function (row) {
return (
<Event key={row.WhatId} data={row} />
);
})}
</tbody>
</table>
)
}
});
This is Event:
Event = React.createClass({
displayName: 'Event',
render() {
return (
<tr>
{_.keys(this.props.data).map((x) => {
if (x !== 'attributes')
return <td>{this.props.data[x]}</td>;
})}
</tr>
)
}
});
Question
Clearly I've got the key prop on the <Event /> component. And I'm following the convention that you're supposed to include key on the component, not on the HTML itself (in other words, HTML tags within the Event component). Per the official React docs:
The key should always be supplied directly to the components in the array, not to the container HTML child of each component in the array:
I'm severely confused. Why am I getting warnings?
Have you tried adding a key to the <th> tag?
<tr>
{_.keys(this.props.list[0]).map(function (key) {
if (key !== 'attributes') {
return <th key={key}>{key}</th>;
}
})}
</tr>
I ended up solving it when I realized because I had a <React.Fragment> which also needs a unique key.
tl;dr
Every time you render a list (use map), add a unique key attribute to the list elements (the topmost or "root" element returned from map's callback):
render() {
return (
<div>
{this.props.data.map( element => {
// Place the key on to the returned "root" element.
// Also, in real life this should be a separate component...
return <div key={element.id}>
<span>Id (Name): </span>
<span>{element.id} </span>
<span>({element.name})</span>
</div>;
})}
</div>
)
}
Explanation
Understanding keys in React
The official Lists and Keys documentation shows how you should work with lists and the linked reconciliations doc tells the whys.
Basically when React rerenders a component it runs a diff algorithm that finds out what changed between the new and the previous version of the list. Comparison is not always trivial, but if there is a unique key in each element, it can be clearly identified what has changed. See the example in the doc:
<!-- previous -->
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
<!-- new -->
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
It is clear that a new element with the key 2014 was added, since we have all the other keys and those weren't changed. Without the keys this would be obscure.
Selecting a proper key
From now it is easy to see:
Why it is important that the key should be unique but only between the siblings in the list, because the comparison happens only within the given list's previous and new elements.
The key should remain the same for the same element between the previous and the new version, otherwise we would compare different elements and wouldn't be able to track change. That is why it is advised to use the id of the resource or (if it doesn't have one) some other data that is unique to the element, and why you shouldn't use things like Math.random().
Placing key attribute on components
The convention that you should place the key attribute to a component is more of a good practice, because when you iterate a list and want to render an element, that clearly indicates that you should organize that code to a separate component.
Setting the key attribute in the loop
The statement you quoted from the docs:
The key should always be supplied directly to the components in the array, not to the container HTML child of each component in the array:
Means that if you render components in a loop, then you should set the key attribute of the component in the loop, like you did it in your EventsTable component:
{this.props.list.map(function (row) {
return (
<Event key={row.WhatId} data={row} />
);
})}
The wrong way is to pass it down to the component where it would set the key on itself:
Event = React.createClass({
displayName: 'Event',
render() {
// Don't do this!
return (
<tr key={this.props.data.WhatId}>
{_.keys(this.props.data).map((x) => {
There is another good example for this in this article.
Check if variable that you pass to key is defined, because if it's undefined then error will be same, but it looks like code should work.
I had the problems too, and fixed it after follwing link.
like:
{_data.map(function(object, i){
return <div className={"row"} key={i}>
{[ object.name ,
<b className="fosfo" key={i}> {object.city} </b> , // remove the key
object.age
]}
</div>;
})}
The easiest fix for this is to create a separate component for the items you're mapping and add the key to that component.
Create a new component above your existing component (or link to it your call).
const TableDataComponent = ({ k }) => {
return (
<th>{k}</th>
)
}
Then in your code add that component with your key:
<tr>
{arr.map((k) => {
return <TableDataComponent key={k._id} k={k} />
})}
</tr>
Related
i am using react and when i tried to use map function in it ,i had multiple components inside map return function.so inorder to contain i used dummy parent element and started showing the error ** Each child in a list should have a unique "key" prop** .i don't know how to include key in the dummy parent <>.will you help me out.
{
dataArray.map((item,key)=>{
return(
<>
<td key={key}>{item.country}</td>
<td key={key}>{item.city}</td>
</>
)})
}
By adding the dummy parent i am getting that error unique key.is there any way i can remove the unique key error keeping both td in the return statement
Add a key to the fragment, not to the individual <td>s:
{
dataArray.map((item, key) => {
return (
<React.Fragment key={key}>
<td>{item.country}</td>
<td>{item.city}</td>
</React.Fragment>
);
})
}
I am currently designing an application that contains a list of values in a list, called modifiers, to be edited by the user to then store for later use in calculations. To make it easier to find a specific modifier, I added a search function to the list in order to pull up the similar modifiers together to the user. However, once the user puts in a value into the filtered list and then unfilters the list, the component incorrectly assigns the values to the wrong modifiers. To be more specific, the ant design <List> component when filtered fails to put the proper defaultValue for each associated input. Namely, when I input a value into the first item in the filtered list and then unfilter it, the List incorrectly places that value within the first element on the unfiltered list, rather than the modifier it was supposed to be associated with. It should be putting the proper value with the associated element by assigning the value that its grouped with in the context I have stored, but it obviously fails to do so.
Here is the Ant Design List Component I am talking about, I have removed some callbacks that aren't necessary to understand the problem. The renderitem prop takes the dataSource prop as an input and maps all of the values into it to be inputs for the <List.Item> components.
EDIT:
I failed to mention the hook in the first line, that is utilized by the search function in order to filter the words looked through to update the list accordingly. I also removed some unnecessary inline css and components since they are not relevant to the problem to improve readability. I have also decided to give a more concrete example of my issue:
This is an image of the initial values set by the user.
This is an image immediately after searching the exact name of the modifier and the list gets filtered. Clearly, the value from the first item of the unfiltered list is being put into the input of the first item of the filtered list, which is the main problem. Now when the search is undone, everything does get properly set, so I am unsure how to fix this.
I have some ideas as to why this is occurring. I know that the input components are not being re-rendered, and rather their ids are just being swapped out when the search occurs. So if there are any ways to either forcefully re-render the input components in addition to the list sections, please tell me!
const Modifiers = () => {
const [searchFilter, setSearchFilter] = useState(military); //Only for words in search bar, "military" will be replaced with entire data set later
const context = useContext(Context)
const search = value => {
if(value != ""){
setSearchFilter(searchFilter.filter(mod => mod.name.toLowerCase().indexOf(value.toLowerCase()) != -1))
}
else {
setSearchFilter(military)
}
}
const updateContext = (e, name) => {
let id = name.toLowerCase().replace(/ /gi, "_");
if(context.modifiers[id] != undefined){
context.modifiers[id] = parseFloat(e.target.value)
}
}
return (
<Layout>
<SiteHeader/>
<Content style={{ padding: '1% 3%', backgroundColor: "white"}}>
<Typography>
<Title level={2} style={{textAlign: "center"}}>
Modifier List
</Title>
</Typography>
<List dataSource={searchFilter} header={<div style={{display: "flex"}}> <Title level={3} style={{paddingLeft: "24px"}}>Modifiers</Title> <Button shape="circle" size="large" icon={<InfoCircleOutlined/>}/> <Search allowClear placeholder="Input Modifier Name" enterButton size="large" onSearch={search}
renderItem={mod => (
<List.Item extra={parseTags(mod)}>
<List.Item.Meta description={mod.desc} avatar={<Avatar src={mod.image}/>} title={<div style={{display: "flex"}}><Title level={5}>{mod.name}: </Title> <Input defaultValue={context.modifiers[mod.name.toLowerCase().replace(/ /gi, "_")] != undefined ? context.modifiers[mod.name.toLowerCase().replace(/ /gi, "_")] : ""} id={mod.name} onChange={(e) => updateContext(e, mod.name)}/></div>}/>
</List.Item>
)}
/>
</Content>
</Layout>
);
}
export default Modifiers;
Here is the Context Class, the modifiers field is what is the issue currently. It only has 2 currently, but the problem persists when more are added, and these 2 modifiers are the first in the unfiltered list as well.
export class Provider extends React.Component {
state = {
name: "None Selected",
tag: String,
flag: "images/flags/ULM",
modifiers: {
army_tradition: 0,
army_tradition_decay: 0,
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
Here is what one element in the military array looks like for reference as well. The regex inside the <List.Item> component is merely converting the name field of the object into one that matches whats stored within the context.modifiers field.
export const military = [
{
name: "Army Tradition",
desc: "Adds to the rate of army tradition gained each year.",
function: "ADDITIVE",
type: "WHOLE NUMBER",
category: "MILITARY",
image: "/images/icons/landLeaderFire.png",
},
...
Thanks for any help you can give.
I have solved the issue, I replaced the "id" prop with a "key" prop (which the documentation doesn't even tell you about) and now everything works properly!
Currently I'm working on a react project, but I'm seeing some unexpected behavior when sorting an array of stateful child components.
If I have a parent component
export function Parent(){
const [children, setChildren] = useState([
{name:'Orange',value:2},
{name:'Apple',value:1},
{name:'Melon',value:3}
])
var count = 0
function handleSort() {
var newChildren=[...children]
newChildren.sort((a,b)=>{return a.value-b.value})
setChildren(newChildren)
}
return (
<div>
<button onClick={handleSort}>Sort</button>
{children.map((child) => {
count++
return(<ChildComp key={count} details={child}/>)
})}
</div>
)
}
And a child component
function ChildComp(props){
const[intCount,setIntCount] = useState(0)
function handleCount(){
setIntCount(intCount+1)
}
return (
<div>
<p>{props.details.name}</p>
<button onClick={handleCount}>{intCount}</button>
</div>
)
}
When the page first renders everything looks great, three divs render with a button showing the number of times it was clicked and the prop name as it was declared in the array. I've noticed that when I sort, it sorts the props being passed to the child components which then rerender, but the intCount state of the child component stays tied to the original location and is not sorted. is there any way to keep the state coupled with the array element through the sort while still maintaining state data at the child level, or is the only way to accomplish this to raise the state up to the parent component and pass a callback or dispatch to the child to update it?
The count is not is not sorted. It just got updated when you sorted.
Keys help React identify which items have changed, are added, or are
removed. Keys should be given to the elements inside the array to give
the elements a stable identity
Every time you sort, key stay the same, as you use count.
Try using value as key
export function Parent(){
// ....
return (
<div>
<button onClick={handleSort}>Sort</button>
{children.map(child => {
return <ChildComp key={child.value} details={child}/> // key is important
})}
</div>
)
}
More info: https://reactjs.org/docs/lists-and-keys.html#keys
My ListItem.jslooks like this:
render() {
return(
<li key={parseInt(this.props.keyProp)}>
<button onClick={this.props.onClick}
className="sidebar__button">
{this.props.text}
</button>
</li>
);
}
When I separately place {parseInt(this.props.keyProp)}, it shows a number.
But when I equate it to key property of the <li>, I get the error Warning: Each child in a list should have a unique "key" prop. and I also cannot see any key when I inspect element on the <li>.
I am sharing the code which uses ListItem.js
populateLi = () => {
let LIs = []
for (var keyItem in graphs) {
if (graphs.hasOwnProperty(keyItem)) {
LIs.push(<ListItem keyProp={keyItem} onClick={this.onClick} text={graphs[keyItem][0]["Scheme Name"]}/>)
}
}
return LIs;
}
The key prop should be added to the function that is responsible for rendering ListItem.js. So:
items.map((item) => KeyItem key={item.id} item={item} />
As per the react documentation, the "key" prop is a special string attribute that is used for optimization and identification. It does not appear in the HTML.
The error you are getting is due to the fact that each list element does not have a unique key. This should be fixed by assigning a unique key to each element.
More info here:
https://reactjs.org/docs/lists-and-keys.html
The key is supposed to be ListItem itself not on the <li> inside ListItem
LIs.push(<ListItem key={keyItem} onClick={this.onClick} text={graphs[keyItem][0]["Scheme Name"]}/>)
That fixes the problem.
The code below contains an array.map function what is the function of term and i and where was it gotten from, and what does the array.map and the onchange do
import React, { Component } from 'react';
class Apps extends Component {
componentDidMount() {
}
iLikeFunctions() {
console.log('yay functions');
}
render() {
var array = ['here','we','go'];
var no = 'yes';
const display = 'My Name';
return (
<div>
<p>{display}</p>
<hr />
<input type="text" onChange={this.iLikeFunctions} />
<table>
<tbody>
{array.map((term,i) => {
no = 'no';
return (
<tr key={i}>
<td>{term}</td>
<td>{no}</td>
</tr>
)
})}
</tbody>
</table>
</div>
);
}
}
export default Apps;
Map:
The map() method creates a new array with the results of calling a provided function on every element in the calling array. So in the following line:
array.map((term,i)
You are mapping the array called array and looping through the array, assigning the word term for each value in the array and return a tr element for each array element with their respective value, index and variable string printed on the <tr>.
Key:
i is the index of the respective value which acts as a key since you didn't specify unique key ids for the elements.
A "key" is a special string attribute you need to include when creating lists of elements. Keys help React identify which items have changed, are added, or are removed.
Do note that it is not recommended to use indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state.
Check out the keys section in the official React Docs for a more in-depth explanation of keys.
onchange:
onchange watches the input field for any change and when it detects a change, it runs the iLikeFunctions().
tldr: The above code loops through array ['here','we','go']; and returns a <tr> for each value. It also runs the iLikeFunctions() whenever the input field value is changed.