Reputation: 417
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!
Upvotes: 2
Views: 4306
Reputation: 487
Here are a couple of things to consider:
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});
}
Upvotes: 1
Reputation: 29989
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.
Upvotes: 4
Reputation: 2214
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} />
Upvotes: -1