Understanding the use of keys in React - reactjs

I have this simple example of list rendering in React:
render() {
return (
<ul>
{this.state.cards.map(({ data }, index) => (
<li key={index} onClick={() => this.remove(index)}>
{data}
</li>
))}
</ul>
);
}
remove = index => {
let cards = this.state.cards.filter((card, i) => i != index);
this.setState({ cards });
};
Here I use the index as key and there is something I don't understand even after searching keys in React in google and reading the manual:
The remove function changes the indices which in turn changes the keys of some elements in the list (so if I removed number 2 all the keys of the elements afters it changes).
So I'd actually expect this function to always remove the last item in the list, since only this key is removed each time, but surprisingly or not it works the way it "should" and removes the right element each time -- when I look at the React console in Chrome I see it changes keys but doesn't delete and create again those elements which had their keys changed...
I am very confused about what's happening here ((⇀‸↼))

Related

Why does removing the correct item in state array, not delete the right component in the UI? - React

When I console.log the index that is being deleted it shows the correct index to be deleted, but the behaviour does not correspond - it always only deletes the last item in the array on the UI. (but in the state array it is deleting the correct index)
example:
(refer to image below)
When I click on delete for the first item (index 0), it removes the last item (index 3) from the UI. But upon viewing the console.log, it actually removed the correct item (index 0) from the state array.
How it looks in the UI:
Note that the red numbers are there just for me to know if the array index is correct. (and it is working correctly)
EditUser.js
// holds the array of data for each RoleMapInput component
const [roleMaps, setRoleMaps] = useState([{organisation:[], roles:[], type:[]}]);
// Used to add a RoleMapInput component to the UI
const handleAddRoleMap = () =>{
setRoleMaps((oldRoleMaps) => [...oldRoleMaps,{organisation:[], roles:[], type:[]}])
}
// Used to delete a RoleMapInput component from the UI
const handleDelRoleMap = (delIndex) =>{
console.log("delIndex: ", delIndex)
setRoleMaps((oldRoleMaps) => {
console.log(oldRoleMaps)
return oldRoleMaps.filter((_,index)=>index !== delIndex)
})
...
...
...
return(
...
// Where the RoleMapInput component is duplicated based on the array roleMaps
{console.log("roleMaps at grid: ",roleMaps)}
{roleMaps.map((roleMap, index) => (
<RoleMapInput key={index} roleMapIndex={index} roleMap={roleMap} handleDelRoleMap={handleDelRoleMap}/>
))}
...
)
RoleMapInput.js
export default function RoleMapInput(props) {
...
...
return (
<>
<Grid item xs={1}>
<FormControl fullWidth>
<IconButton aria-label="delete" color='error' onClick={()=>props.handleDelRoleMap(props.roleMapIndex)}>
{props.roleMapIndex}
<DeleteOutlineOutlinedIcon/>
</IconButton>
</FormControl>
</Grid>
</>
);
}
Without the minimum reproducible example is really hard to tell. But I can tell you this: the filter function is working properly, but using as react docs mention:
We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state.
You can read more about this here
A possible explanation is that when you are removing the element on pos 0 react might consider that it doesn't need to re-render any child component, but rather remove the last element (because you had rendered <RoleMapInput components with keys 0,1,2,3 and after removing any item from the array you are left with keys 0,1,2)

Where to indicate the key property when mapping an array in React? [duplicate]

I'm building a React component that accepts a JSON data source and creates a sortable table.
Each of the dynamic data rows has a unique key assigned to it but I'm still getting an error of:
Each child in an array should have a unique "key" prop.
Check the render method of TableComponent.
My TableComponent render method returns:
<table>
<thead key="thead">
<TableHeader columns={columnNames}/>
</thead>
<tbody key="tbody">
{ rows }
</tbody>
</table>
The TableHeader component is a single row and also has a unique key assigned to it.
Each row in rows is built from a component with a unique key:
<TableRowItem key={item.id} data={item} columns={columnNames}/>
And the TableRowItem looks like this:
var TableRowItem = React.createClass({
render: function() {
var td = function() {
return this.props.columns.map(function(c) {
return <td key={this.props.data[c]}>{this.props.data[c]}</td>;
}, this);
}.bind(this);
return (
<tr>{ td(this.props.item) }</tr>
)
}
});
What is causing the unique key prop error?
You should add a key to each child as well as each element inside children.
This way React can handle the minimal DOM change.
In your code, each <TableRowItem key={item.id} data={item} columns={columnNames}/> is trying to render some children inside them without a key.
Check this example.
Try removing the key={i} from the <b></b> element inside the div's (and check the console).
In the sample, if we don't give a key to the <b> element and we want to update only the object.city, React needs to re-render the whole row vs just the element.
Here is the code:
const data = [
{ name: "Nuri", age: 28, city: "HO" },
{ name: "Talib", age: 82, city: "HN" },
{ name: "Jenny", age: 41, city: "IT" },
];
const ExampleComponent = React.createClass({
render: function () {
const infoData = this.props.info;
return (
<div>
{infoData.map((object, i) => {
return (
<div className={"row"} key={i}>
{[
object.name,
// remove the key
<b className="fosfo" key={i}>
{" "}
{object.city}{" "}
</b>,
object.age,
]}
</div>
);
})}
</div>
);
},
});
React.render(<ExampleComponent info={data} />, document.body);
The answer posted by #Chris at the bottom goes into much more detail than this answer.
React documentation on the importance of keys in reconciliation: Keys
Be careful when iterating over arrays!!
It is a common misconception that using the index of the element in the array is an acceptable way of suppressing the error you are probably familiar with:
Each child in an array should have a unique "key" prop.
However, in many cases it is not! This is anti-pattern that can in some situations lead to unwanted behavior.
Understanding the key prop
React uses the key prop to understand the component-to-DOM Element relation, which is then used for the reconciliation process. It is therefore very important that the key always remains unique, otherwise there is a good chance React will mix up the elements and mutate the incorrect one. It is also important that these keys remain static throughout all re-renders in order to maintain best performance.
That being said, one does not always need to apply the above, provided it is known that the array is completely static. However, applying best practices is encouraged whenever possible.
A React developer said in this GitHub issue:
key is not really about performance, it's more about identity (which in turn leads to better performance). randomly assigned and changing values are not identity
We can't realistically provide keys [automatically] without knowing how your data is modeled. I would suggest maybe using some sort of hashing function if you don't have ids
We already have internal keys when we use arrays, but they are the index in the array. When you insert a new element, those keys are wrong.
In short, a key should be:
Unique - A key cannot be identical to that of a sibling component.
Static - A key should not ever change between renders.
Using the key prop
As per the explanation above, carefully study the following samples and try to implement, when possible, the recommended approach.
Bad (Potentially)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
This is arguably the most common mistake seen when iterating over an array in React. This approach isn't technically "wrong", it's just... "dangerous" if you don't know what you are doing. If you are iterating through a static array then this is a perfectly valid approach (e.g. an array of links in your navigation menu). However, if you are adding, removing, reordering or filtering items, then you need to be careful. Take a look at this detailed explanation in the official documentation.
class MyApp extends React.Component {
constructor() {
super();
this.state = {
arr: ["Item 1"]
}
}
click = () => {
this.setState({
arr: ['Item ' + (this.state.arr.length+1)].concat(this.state.arr),
});
}
render() {
return(
<div>
<button onClick={this.click}>Add</button>
<ul>
{this.state.arr.map(
(item, i) => <Item key={i} text={"Item " + i}>{item + " "}</Item>
)}
</ul>
</div>
);
}
}
const Item = (props) => {
return (
<li>
<label>{props.children}</label>
<input value={props.text} />
</li>
);
}
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>
In this snippet we are using a non-static array and we are not restricting ourselves to using it as a stack. This is an unsafe approach (you'll see why). Note how as we add items to the beginning of the array (basically unshift), the value for each <input> remains in place. Why? Because the key doesn't uniquely identify each item.
In other words, at first Item 1 has key={0}. When we add the second item, the top item becomes Item 2, followed by Item 1 as the second item. However, now Item 1 has key={1} and not key={0} anymore. Instead, Item 2 now has key={0}!!
As such, React thinks the <input> elements have not changed, because the Item with key 0 is always at the top!
So why is this approach only sometimes bad?
This approach is only risky if the array is somehow filtered, rearranged, or items are added/removed. If it is always static, then it's perfectly safe to use. For example, a navigation menu like ["Home", "Products", "Contact us"] can safely be iterated through with this method because you'll probably never add new links or rearrange them.
In short, here's when you can safely use the index as key:
The array is static and will never change.
The array is never filtered (display a subset of the array).
The array is never reordered.
The array is used as a stack or LIFO (last in, first out). In other words, adding can only be done at the end of the array (i.e push), and only the last item can ever be removed (i.e pop).
Had we instead, in the snippet above, pushed the added item to the end of the array, the order for each existing item would always be correct.
Very bad
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
While this approach will probably guarantee uniqueness of the keys, it will always force react to re-render each item in the list, even when this is not required. This a very bad solution as it greatly impacts performance. Not to mention that one cannot exclude the possibility of a key collision in the event that Math.random() produces the same number twice.
Unstable keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.
Very good
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
This is arguably the best approach because it uses a property that is unique for each item in the dataset. For example, if rows contains data fetched from a database, one could use the table's Primary Key (which typically is an auto-incrementing number).
The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys
Good
componentWillMount() {
let rows = this.props.rows.map(item => {
return {uid: SomeLibrary.generateUniqueID(), value: item};
});
}
...
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uid} />;
})}
</tbody>
This is also a good approach. If your dataset does not contain any data that guarantees uniqueness (e.g. an array of arbitrary numbers), there is a chance of a key collision. In such cases, it is best to manually generate a unique identifier for each item in the dataset before iterating over it. Preferably when mounting the component or when the dataset is received (e.g. from props or from an async API call), in order to do this only once, and not each time the component re-renders. There are already a handful of libraries out there that can provide you such keys. Here is one example: react-key-index.
This may or not help someone, but it might be a quick reference. This is also similar to all the answers presented above.
I have a lot of locations that generate list using the structure below:
return (
{myList.map(item => (
<>
<div class="some class">
{item.someProperty}
....
</div>
</>
)}
)
After a little trial and error (and some frustrations), adding a key property to the outermost block resolved it. Also, note that the <> tag is now replaced with the <div> tag now.
return (
{myList.map((item, index) => (
<div key={index}>
<div class="some class">
{item.someProperty}
....
</div>
</div>
)}
)
Of course, I've been naively using the iterating index (index) to populate the key value in the above example. Ideally, you'd use something which is unique to the list item.
Check: key = undef !!!
You got also the warn message:
Each child in a list should have a unique "key" prop.
if your code is complete right, but if on
<ObjectRow key={someValue} />
someValue is undefined!!! Please check this first. You can save hours.
Just add the unique key to the your Components
data.map((marker)=>{
return(
<YourComponents
key={data.id} // <----- unique key
/>
);
})
You should use a unique value for each children key of tbody where
the value cannot not be identical (same) to its sibling
should not change between renders
For example, the key value can be database id or UUID (Universal Unique Identifier).
Here the keys are handling manually:
<tbody>
{rows.map((row) => <ObjectRow key={row.uuid} />)}
</tbody>
You can also let React handle the keys using React.Children.toArray
<tbody>
{React.Children.toArray(rows.map((row) => <ObjectRow />))}
</tbody>
Here are the React docs that explain well using the Key property, the key should be defined at the parent component it should not be used inside the child component.React Docs
Warning: Each child in an array or iterator should have a unique "key" prop.
This is a warning as for array items which we are going to iterate over will need a unique resemblance.
React handles iterating component rendering as arrays.
Better way to resolve this is provide index on the array items you are going to iterate over.for example:
class UsersState extends Component
{
state = {
users: [
{name:"shashank", age:20},
{name:"vardan", age:30},
{name:"somya", age:40}
]
}
render()
{
return(
<div>
{
this.state.users.map((user, index)=>{
return <UserState key={index} age={user.age}>{user.name}</UserState>
})
}
</div>
)
}
index is React built-in props.
When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort:
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
Please refer to List and Keys - React
In ReactJS if you are rendering an array of elements you should have a unique key for each those elements. Normally those kinda situations are creating a list.
Example:
function List() {
const numbers = [0,1,2,3];
return (
<ul>{numbers.map((n) => <li> {n} </li>)}</ul>
);
}
ReactDOM.render(
<List />,
document.getElementById('root')
);
In the above example, it creates a dynamic list using li tag, so since li tag does not have a unique key it shows an error.
After fixed:
function List() {
const numbers = [0,1,2,3];
return (
<ul>{numbers.map((n) => <li key={n}> {n} </li>)}</ul>
);
}
ReactDOM.render(
<List />,
document.getElementById('root')
);
Alternative solution when use map when you don't have a unique key (this is not recommended by react eslint ):
function List() {
const numbers = [0,1,2,3,4,4];
return (
<ul>{numbers.map((n,i) => <li key={i}> {n} </li>)}</ul>
);
}
ReactDOM.render(
<List />,
document.getElementById('root')
);
Live example: https://codepen.io/spmsupun/pen/wvWdGwG
If we have array object data . then we map to show the data . and pass the unique id (key = {product.id} ) because browser can select the unique data.
example : [
{
"id": "1",
"name": "walton glass door",
"suplier": "walton group",
"price": "50000",
"quantity": "25",
"description":"Walton Refrigerator is the Best Refrigerator brand in bv
Bangladesh "
},
{
"id": "2",
"name": "walton glass door",
"suplier": "walton group",
"price": "40000",
"quantity": "5",
"description":"Walton Refrigerator is the Best Refrigerator brand in
Bangladesh "
},
}
now we are mapping the data and pass the unique id:
{
products.map(product => <product product={product} key={product.id}
</product>)
}
According to React docs, each row/item should have a unique key.
Keys help React identify which items have changed, are added, or are removed.
Personally, I prefer using the crypto interface to generate a random UUID:
(crypto is built-in in vanilla-js)
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={crypto.randomUUID()}>item {number}
</li>
);
Best solution of define unique key in react:
inside the map you initialized the name post then key define by key={post.id} or in my code you see i define the name item then i define key by key={item.id}:
<div className="container">
{posts.map(item =>(
<div className="card border-primary mb-3" key={item.id}>
<div className="card-header">{item.name}</div>
<div className="card-body" >
<h4 className="card-title">{item.username}</h4>
<p className="card-text">{item.email}</p>
</div>
</div>
))}
</div>
I was running into this error message because of <></> being returned for some items in the array when instead null needs to be returned.
I had a unique key, just had to pass it as a prop like this:
<CompName key={msg._id} message={msg} />
This page was helpful:
https://reactjs.org/docs/lists-and-keys.html#keys
In my case, set id to tag
<tbody key={i}>
The problem is solved.
A visual explanation.
The incorrect way key=index (of an array)
As you can see, label 3, label 2, and label 1 ALL got re-rendered (flashing in the Elements panel).
The correct way key=uniqueId
Only the top new element flashes (gets re-rendered).
This is a warning, But addressing this will make Reacts rendering much FASTER,
This is because React needs to uniquely identify each items in the list. Lets say if the state of an element of that list changes in Reacts Virtual DOM then React needs to figure out which element got changed and where in the DOM it needs to change so that browser DOM will be in sync with the Reacts Virtual DOM.
As a solution just introduce a key attribute to each li tag. This key should be a unique value to each element.
var TableRowItem = React.createClass({
render: function() {
var td = function() {
return this.props.columns.map(function(c, i) {
return <td key={i}>{this.props.data[c]}</td>;
}, this);
}.bind(this);
return (
<tr>{ td(this.props.item) }</tr>
)
}
});
This will sove the problem.
If you are getting error like :
> index.js:1 Warning: Each child in a list should have a unique "key" prop.
Check the render method of `Home`. See https://reactjs.org/link/warning-keys for more information.
Then Use inside map function like:
{classes.map((user, index) => (
<Card **key={user.id}**></Card>
))}`enter code here`
This is a simple example,I have used a react condition with && first then map, in the I have added the key the user id to be sure that it's unique
<tbody>
{users &&
users.map((user) => {
return <tr key={user._id}>
<td>{user.username}</td>
<td><input
name="isGoing"
type="checkbox"
checked={user.isEnabled}
onChange={handleInputChangeNew}/></td>
<td>{user.role.roleTitle} - {user.role.department.departmentName}</td>
{/*<td className="text-right">
<Button>
ACTION
</Button>
</td>*/}
</tr>
})
}
</tbody>
your key should be unique.like an unique id.And your code should be like this
<div>
{products.map(product => (
<Product key={product.id}>
</Product>
))}
</div>
I don't go with the detail explanation but key to this answer is "key"
just put the key attribute in your tag and ensure that every-time you iterate you give unique value to it
#ensure that key's value is not clashing with others
Example
<div>
{conversation.map(item => (
<div key={item.id } id={item.id}>
</div>
))}
</div>
where conversation is an array something like below :
const conversation = [{id:"unique"+0,label:"OPEN"},{id:"unique"+1,label:"RESOLVED"},{id:"unique"+2,label:"ARCHIVED"},
]
I think when working with tables (or in similar examples), creating a unique key should be passed to child component from the parent component for the sake of REUSABILITY.
Because if you are creating a table, that means you are passing data from the parent. If you assign key={row.name} maybe currently data has name property but if you want to use this table component somewhere else you assume that in each row of data that you have passed, you have name property.
Since the engineer will be preparing the data in the parent component, the engineer should create a key function based on the data.
const keyFunc = (student) => {
return student.id;
};
In this case engineer knows what data it is sending, it knows that each row has id property which is unique. Maybe in the different data set, the data set is stock prices and it does not have "id" property but "symbol"
const keyFunc = (stock) => {
return stock.symbol;
};
this keyFunc should be passed to the child component as a prop to guarantee the reusability and uniqueness.
The "Each child in a list should have a unique "key" prop." warning happens in React when you create a list of elements without the special key attribute. Keys must be assigned to each element in a loop to give stable identity to elements in React.
We can set the id property of the object as a unique key.
export default function App() {
const posts = [
{ id: 1, title: "First "},
{ id: 2, title: "Second" },
{ id: 3, title: "Third" }
];
return (
<div>
<ul>
{posts.map(value =>
<li key={value.id}>{value.title}</li>
)}
</ul>
</div>
);}
//simple way
//if u using ant design remove the empty fragment...
//worng ans---> change to following crt ans
export default function App() {
const posts = [
{ id: 1, title: "First "},
{ id: 2, title: "Second" },
{ id: 3, title: "Third" }
];
{fields.map((field,index)=>{
return(
<> //empty fragment
<Row key={index}>
<Col span={6}>hello</Col>
</Row>
</>
)
})}
//correct ans
//remove the empty fragments after solve this key.prop warning problem
export default function App() {
const posts = [
{ id: 1, title: "First "},
{ id: 2, title: "Second" },
{ id: 3, title: "Third" }
];
{fields.map((field,index)=>{
return(
<> //empty fragment
<Row key={index}>
<Col span={6}>hello</Col>
</Row>
</>
)
})}
I faced a similar problem but not exact. Tried every possible solution and couldn't get rid of that error
Each child in an array should have a unique "key" prop.
Then I tried opening it in a different local host. I don't know how, but it worked!
If you are struggling with this error Each child in a list should have a unique "key" prop.
Solve by declaring index value to the key attribute inside the rendering element.
App.js component
import Map1 from './Map1';
const arr = [1,2,3,4,5];
const App = () => {
return (
<>
<Map1 numb={arr} />
</>
)
}
export default App
Map.js component
const Map1 = (props) => {
let itemTwo = props.numb;
let itemlist = itemTwo.map((item,index) => <li key={index}>{item}</li>)
return (
<>
<ul>
<li style={liStyle}>{itemlist}</li>
</ul>
</>
)
}
export default Map1

How can you generate predictable react keys for items without unique ids?

At times, I map over collections without unique ids. In that case, react logs this warning to the console: Each child in an array or iterator should have a unique "key" prop. To solve this issue, I created the following es6 module.
const key = {
count: 0,
getKey: () => {
key.count += 1;
return key.count;
},
};
export default key;
Now, I import the key module throughout my React application and call its getKey method when I need a unique key. But, I don't feel like the keys this generates are predictable like React advises. While the first render might map a collection with keys 1-10 predictably, any render afterwards will generate a new key for that element. Using a package like UUID will have the same effect will it not? What is the proper way to generate predictable React keys? Am I misunderstanding what they mean by predictable? Thanks!
Generating the keys before rendering the component is the best way to end up with predictable ones.
In an ideal world you will be working with lists of objects that already have their own unique id and in those cases it's best to use those ids as keys.
If you were rendering a list of numbers that might include duplicates, you could give them each a fixed key before passing them to a component to render.
let numbers = [1, 1, 2, 3];
let numbersWithKeys = numbers.map(x => {
return { value: x, key: key.getKey() };
});
function Numbers({ numbers }) {
return (
<ul>
{numbers.map(number => <li key={number.key}>{number.value}</li>)}
</ul>
)
}
render(
<Numbers numbers={numbersWithKeys} />
);
Then every single time your component renders it can safely use the same key for each number. You can imagine this component rendering the following virtual tree:
<ul>
<li key={1}>1</li>
<li key={2}>1</li>
<li key={3}>2</li>
<li key={4}>3</li>
</ul>
It would be possible for the <Numbers> component to reverse the list and you'd still end up with the same key for each item. That's what React means by predictable.
React can make some performance shortcuts when you change the order of a list by moving the rendered elements around, rather than re-rendering the entire thing, but if you're generating your keys inside your render method, then it sees a brand new list each time and has to re-render it all.
Here are a couple of things to consider:
Unique: Keys must be unique, but for the current array only.
Predictable: Keys must consistently map to the same element.
Keys do not have to be ridiculously convoluted identifiers.
What's predictable? If we start with (index:key): 1:1,2:2,3:3
and we remove the element on index 2, we should be left with: 1:1,2:3. The item at index 2 with key 2 has been removed, and the item on index 3 has moved to index 2, but retained its key 3.
The simplest way is of course to use a unique property in your data, like your database id (primary key), but that's not always possible. Let's say you want to create a dynamic form with input fields that can be added or removed. The input field elements may look exactly the same, except for a unique identifier which is dynamically assigned. The simplest way is of course using the map index, but that's not predictable, since it will change if you remove the second of three fields, for example.
What are our options?
One solution is to track your elements in state.
class MyComponent extends Component {
constructor() {
super();
this.state = {
myArr: []
};
}
_addHandler = el => {
const arr = this.state[el];
const length = arr.length;
const lastId = length > 0 ? arr[length - 1] : 0;
this.setState({ [el]: [...arr, lastId + 1] });
};
_removeHandler = (el, i) => {
const newItems = [
...this.state[el].slice(0, i),
...this.state[el].slice(i + 1)
];
this.setState({ [el]: newItems });
};
render() {
const myList = this.state.myArr;
return (
<div>
{myList.map((el, i) => (
<p>
{this.state.myArr[i]}{" "}
<span onClick={() => this._removeHandler("myArr", i)}>
remove
</span>
</p>
))}
<p onClick={() => this._addHandler("myArr")}>Add One</p>
</div>
);
}
}
You could write a simple wrapper component to track elements in an array using this concept, if it's something you're going to be doing a lot.
To assign the initial keys you could do the following in componentDidMount()
componentDidMount() {
const {someArr} = this.props;
// use the index to map the initial key values
// after which use state to track the key pairings
const newIds = someArr.map((el,i) => i+1);
this.setState({myArr:newIds});
}
you can use any data specifically related to the item... or (not recommended) for random id you can use new Date().getTime()
key is used for reusing components... it's important to give the same key for the same item
[].map(item, index) => <Item key={'this_list_specific_name' + item.id} />

error: Do not use Array index in keys

I am using index to generate key in a list. However, es-lint generates an error for the same. React doc also states that using the item index as a key should be used as last resort.
const list = children.map((child, index) =>
<li key={index}> {child} </li>);
I considered using react-key-index.
npm install react-key-index gives following error:
npm ERR! code E404
npm ERR! 404 Not Found: react-key-index#latest
Are there any suggestions on other packages that allow to generate unique key? Any suggestion on react key generator is appreciated!
When you use index of an array as a key, React will optimize and not render as expected. What happens in such a scenario can be explained with an example.
Suppose the parent component gets an array of 10 items and renders 10 components based on the array. Suppose the 5th item is then removed from the array. On the next render the parent will receive an array of 9 items and so React will render 9 components. This will show up as the 10th component getting removed, instead of the 5th, because React has no way of differentiating between the items based on index.
Therefore always use a unique identifier as a key for components that are rendered from an array of items.
You can generate your own unique key by using any of the field of the child object that is unique as a key. Normal, any id field of the child object can be used if available.
Edit : You will only be able to see the behavior mentioned above happen if the components create and manage their own state, e.g. in uncontrolled textboxes, timers etc. E.g. React error when removing input component
The issue with using key={index} happens whenever the list is modified. React doesn't understand which item was added/removed/reordered since index is given on each render based on the order of the items in the array. Although, usually it's rendered fine, there are still situations when it fails.
Here is my example that I came across while building a list with input tags. One list is rendered based on index, another one based on id. The issue with the first list occurs every time you type anything in the input and then remove the item. On re-render React still shows as if that item is still there. This is 💯 UI issue that is hard to spot and debug.
class List extends React.Component {
constructor() {
super();
this.state = {
listForIndex: [{id: 1},{id: 2}],
listForId: [{id: 1},{id: 2}]
}
}
renderListByIndex = list => {
return list.map((item, index) => {
const { id } = item;
return (
<div key={index}>
<input defaultValue={`Item ${id}`} />
<button
style={{margin: '5px'}}
onClick={() => this.setState({ listForIndex: list.filter(i => i.id !== id) })}
>Remove</button>
</div>
)
})
}
renderListById = list => {
return list.map((item) => {
const { id } = item;
return (
<div key={id}>
<input defaultValue={`Item ${id}`} />
<button
style={{margin: '5px'}}
onClick={() => this.setState({ listForId: list.filter(i => i.id !== id) })}
>Remove</button>
</div>
)
})
}
render() {
const { listForIndex, listForId } = this.state;
return (
<div className='flex-col'>
<div>
<strong>key is index</strong>
{this.renderListByIndex(listForIndex)}
</div>
<div>
<strong>key is id</strong>
{this.renderListById(listForId)}
</div>
</div>
)
}
}
ReactDOM.render(
<List />,
document.getElementById('root')
);
.flex-col {
display: flex;
flex-direction: row;
}
.flex-col > div {
flex-basis: 50%;
margin: .5em;
padding: .5em;
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>
Of course, in React, you are required to pass in a unique key value for all elements of an array. Else, you will see this warning in console.
Warning: Each child in an array or iterator should have a unique “key” prop.
So, as a lazy developer, you would simply pass in the loop’s index value as the key value of the child element.
Reordering a list, or adding and removing items from a list can cause issues with the component state, when indexes are used as keys. If the key is an index, reordering an item changes it. Hence, the component state can get mixed up and may use the old key for a different component instance.
What are some exceptions where it is safe to use index as key?
If your list is static and will not change.
The list will never be re-ordered.
The list will not be filtered (adding/removing item from
the list).
There are no ids for the items in the list.
Key should be unique but only among its siblings.
Do not use array indexes as keys, this is an anti-pattern that is pointed out by the React team in their docs.
It's a problem for performance and for state management. The first case applies when you would append something to the top of a list. Consider an example:
<ul>
<li>Element1</li>
<li>Element2</li>
<li>Element3</li>
</ul>
Now, let say you want to add new elements to the top/bottom of the list, then reorder or sort the list (or even worst - add something in the middle). All the index-based key strategy will collapse. The index will be different over a time, which is not the case if for each of these elements there would be a unique id.
CodePens:
Using index as a key: https://reactjs.org/redirect-to-codepen/reconciliation/index-used-as-key
Using ID as a key: https://reactjs.org/redirect-to-codepen/reconciliation/no-index-used-as-key
Play with it and you'll see that at some point the index-based key strategy is getting lost.
In map method ,react render our array element with respect to key. so key plays a vital role. if we use index as key then in this case , if we re-order our components then , we will get problem because react render our array element with respect to key.
For more details go through this video https://www.youtube.com/watch?v=Deu8GE3Xv50&list=PLBHcDEyoOGlQ4o_Nx6FCXmjYlOAF2am6V&index=4&t=363s
use the following lib "react-uuid" : https://www.npmjs.com/package/react-uuid.
react-uuid basically create random ids when you call it each time.
import React from 'react'
import uuid from 'react-uuid'
const array = ['one', 'two', 'three']
export const LineItem = item => <li key={uuid()}>{item}</li>
export const List = () => array.map(item => <LineItem item={item} />)
and this should solve the issue.

React Intro tutorial: how do list's keys get their value?

I have no experience with js and suggested to learn React before React Native. My question comes from code from Intro to React's tutorial.
In tutorial, we made a list named history, with move as its key.
class Game extends React.Component {
constructor() {
super();
this.state = {
history: [{
squares: Array(9).fill(null)
}],
xIsNext: true,
stepNumber: 0,
};
}
[...]
render() {
[...]
const moves = history.map((step, move) => {
const desc = move ?
'Move #' + move :
'Game start';
return (
<li key={move}>
<a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>
</li>
);
});
[...]
The explanation for that part was
[...] React asks you to specify a key property on each element in a list, a string to differentiate each component from its siblings. In this case, alexa, ben, claudia might be sensible keys; if the items correspond to objects in a database, the database ID is usually a good choice:
<li key={user.id}>{user.name}: {user.taskCount} tasks left</li>
[...] It's strongly recommended that you assign proper keys whenever you build dynamic lists. If you don't have an appropriate key handy, you may want to consider restructuring your data so that you do. [...] If you don't specify any key, React will warn you and fall back to using the array index as a key – [...]
Now what I'm confused about, what is move on history above? I can't see anything implying move is assigned a value, yet when it's printed, it shows list index? How come?
The list is being created by using a map method to 'map over' the history array. map will call a function for each element in the array; the second parameter to this callback function is the index of the element in the array.
In your code the callback function looks like this:
(step, move) => {
const desc = move ?
'Move #' + move :
'Game start';
return (
<li key={move}>
<a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>
</li>
);
}
and therefore the index of the array, the second parameter, is called move (you could call it anything); the value of this is then rendered as they key property for each list item.
Description of map here

Resources