Skip to content

[Documentation] Details/Example for Indexes as keys #111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion content/docs/lists-and-keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ const todoItems = todos.map((todo, index) =>
);
```

We don't recommend using indexes for keys if the items can reorder, as that would be slow. You may read an [in-depth explanation about why keys are necessary](/docs/reconciliation.html#recursing-on-children) if you're interested.
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. If you choose not to assign a key to your list items then React will use indexes as keys. You may read an [in-depth explanation about why keys are necessary](/docs/reconciliation.html#recursing-on-children) if you're interested in more information.

### Extracting Components with Keys

Expand Down
6 changes: 5 additions & 1 deletion content/docs/reconciliation.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,11 @@ In practice, finding a key is usually not hard. The element you are going to dis

When that's not the case, you can add a new ID property to your model or hash some parts of the content to generate a key. The key only has to be unique among its siblings, not globally unique.

As a last resort, you can pass item's index in the array as a key. This can work well if the items are never reordered, but reorders will be slow.
As a last resort, you can pass an item's index in the array as a key. This can work well if the items are never reordered, but reorders will be slow.

Reorders can also cause issues with component state when indexes are used as keys. Component instances are updated and reused based on their key. If the key is an index, moving an item changes it. As a result, component state for things like controlled inputs can get mixed up and updated in unexpected ways.

[Here](codepen://reconciliation/index-used-as-key) is an example of the issues that can be caused by using indexes as keys on CodePen, and [here](codepen://reconciliation/no-index-used-as-key) is a updated version of the same example showing how not using indexes as keys will fix these reordering, sorting, and prepending issues.

## Tradeoffs

Expand Down
103 changes: 103 additions & 0 deletions examples/reconciliation/index-used-as-key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const ToDo = (props) => (
<tr>
<td><label>{props.id}</label></td>
<td><input/></td>
<td><label>{props.createdAt.toTimeString()}</label></td>
</tr>
);

class ToDoList extends React.Component {
constructor() {
super();
const date = new Date();
const todoCounter = 1;
this.state = {
todoCounter: todoCounter,
list: [
{ id: todoCounter, createdAt: date },
]
}
}

sortByEarliest() {
const sortedList = this.state.list.sort((a, b) => {
return a.createdAt - b.createdAt;
});
this.setState({
list: [...sortedList]
})
}

sortByLatest() {
const sortedList = this.state.list.sort((a, b) => {
return b.createdAt - a.createdAt;
});
this.setState({
list: [...sortedList]
})
}

addToEnd() {
const date = new Date();
const nextId = this.state.todoCounter + 1;
const newList = [
...this.state.list,
{ id: nextId, createdAt: date }
];
this.setState({
list: newList,
todoCounter: nextId
});
}

addToStart() {
const date = new Date();
const nextId = this.state.todoCounter + 1;
const newList = [
{ id: nextId, createdAt: date },
...this.state.list
];
this.setState({
list: newList,
todoCounter: nextId
});
}

render() {
return(
<div>
<code>key=index</code><br/>
<button onClick={this.addToStart.bind(this)}>
Add New to Start
</button>
<button onClick={this.addToEnd.bind(this)}>
Add New to End
</button>
<button onClick={this.sortByEarliest.bind(this)}>
Sort by Earliest
</button>
<button onClick={this.sortByLatest.bind(this)}>
Sort by Latest
</button>
<table>
<tr>
<th>ID</th><th></th><th>created at</th>
</tr>
{
this.state.list.map((todo, index) => (
<ToDo
key={index}
{...todo}
/>
))
}
</table>
</div>
)
}
}

ReactDOM.render(
<ToDoList />,
document.getElementById('root')
);
103 changes: 103 additions & 0 deletions examples/reconciliation/no-index-used-as-key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const ToDo = (props) => (
<tr>
<td><label>{props.id}</label></td>
<td><input/></td>
<td><label>{props.createdAt.toTimeString()}</label></td>
</tr>
);

class ToDoList extends React.Component {
constructor() {
super();
const date = new Date();
const toDoCounter = 1;
this.state = {
list: [
{ id: toDoCounter, createdAt: date },
],
toDoCounter: toDoCounter
}
}

sortByEarliest() {
const sortedList = this.state.list.sort((a, b) => {
return a.createdAt - b.createdAt;
});
this.setState({
list: [...sortedList]
})
}

sortByLatest() {
const sortedList = this.state.list.sort((a, b) => {
return b.createdAt - a.createdAt;
});
this.setState({
list: [...sortedList]
})
}

addToEnd() {
const date = new Date();
const nextId = this.state.toDoCounter + 1;
const newList = [
...this.state.list,
{ id: nextId, createdAt: date }
];
this.setState({
list: newList,
toDoCounter: nextId
});
}

addToStart() {
const date = new Date();
const nextId = this.state.toDoCounter + 1;
const newList = [
{ id: nextId, createdAt: date },
...this.state.list
];
this.setState({
list: newList,
toDoCounter: nextId
});
}

render() {
return(
<div>
<code>key=id</code><br/>
<button onClick={this.addToStart.bind(this)}>
Add New to Start
</button>
<button onClick={this.addToEnd.bind(this)}>
Add New to End
</button>
<button onClick={this.sortByEarliest.bind(this)}>
Sort by Earliest
</button>
<button onClick={this.sortByLatest.bind(this)}>
Sort by Latest
</button>
<table>
<tr>
<th>ID</th><th></th><th>created at</th>
</tr>
{
this.state.list.map((todo, index) => (
<ToDo
key={todo.id}
{...todo}
/>
))
}
</table>
</div>
)
}
}

ReactDOM.render(
<ToDoList />,
document.getElementById('root')
);