Respond to a unique "key" error
I am going through a tutorial for todo list in React and come across the following error, I wasted quite a lot of time and just can't find the error. There is a bug and code for the component here, and this is the code for the course repo (the problem comes with this commit):
https://github.com/andrewjmead/react-course-todo-app/commit/0521f151705f78cb9f8d69262eb093f1431cb9ca
Any help is greatly appreciated.
Warning: every child element in an array or iterator must have a unique "key" prop. Check out the render method TodoList
. For more information see Fb.me/react-warning-keys.
There is also a bug in the terminal, for the spread operator in the case of TOGGLE_TODO
return {
...todo, // here
completed: nextCompleted,
completedAt: nextCompleted ? moment().unix() : undefined
};
var React = require('react');
var { connect } = require('react-redux');
import Todo from 'Todo';
var TodoAPI = require('TodoAPI');
export var TodoList = React.createClass ({
render: function() {
var { todos, showCompleted, searchText } = this.props;
var renderTodos = () => {
if(todos.length === 0) {
return (
<p className="container__message">No tasks</p>
);
}
return TodoAPI.filterTodos(todos, showCompleted, searchText).map((todo) => {
return (
//add unique key prop to keep track of individual components
<Todo key={todo.id} {...todo} />
);
});
};
return (
<div>
{renderTodos()}
</div>
);
}
});
export default connect(
(state) => {
return state;
}
)(TodoList);
Adapters:
var uuid = require('uuid');
var moment = require('moment');
export var searchTextReducer = (state = '', action) => {
switch (action.type) {
case 'SET_SEARCH_TEXT':
return action.searchText;
default:
return state;
};
};
export var showCompletedReducer = (state = false, action) => {
switch (action.type) {
case 'TOGGLE_SHOW_COMPLETED':
return !state;
default:
return state;
};
};
export var todosReducer = (state = [], action) => {
switch(action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
id: uuid(),
completed: false,
createdAt: moment().unix(),
completedAt: undefined
}
];
case 'TOGGLE_TODO':
return state.map((todo) => {
if(todo.id === action.id) {
var nextCompleted = !todo.completed;
return {
...todo,
completed: nextCompleted,
completedAt: nextCompleted ? moment().unix() : undefined
};
} else {
return todo;
}
});
case 'ADD_TODOS':
return [
...state,
...action.todos
];
default:
return state;
}
};
Webpack:
var webpack = require('webpack');
module.exports = {
entry: [
'script!jquery/dist/jquery.min.js',
'script!foundation-sites/dist/js/foundation.min.js',
'./app/app.jsx'
],
externals: {
jquery: 'jQuery'
},
plugins: [
new webpack.ProvidePlugin({
'$': 'jquery',
'jQuery': 'jquery'
})
],
output: {
path: __dirname,
filename: './public/bundle.js'
},
resolve: {
root: __dirname,
modulesDirectories: [
'node_modules',
'./app/components',
'./app/api'
],
alias: {
applicationStyles: 'app/styles/app.scss',
actions: 'app/actions/actions.jsx',
reducers: 'app/reducers/reducers.jsx',
configureStore: 'app/store/configureStore.jsx'
},
extensions: ['', '.js', '.jsx']
},
module: {
loaders: [
{
loader: 'babel-loader',
query: {
presets: ['react', 'es2015']
},
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/
}
]
},
devtool: 'cheap-module-eval-source-map'
};
source to share
node-uuid is deprecated, check here: https://www.npmjs.com/package/uuid
You can update your .json package by installing uuid and see if it helps:
npm install uuid
Just remember to update var uuid = require ('node-uuid'); to var uuid = require ('uuid'); in other files.
PS Are you getting any errors in your terminal when running webpack?
source to share
In React, when you render multiple equal components (in your case, todos), you need to add a unique key to each one because React needs to know how they will be handled in the virtual house.
Anything you can do to fix this:
-
In the for loop, create an index variable and increment it by 1 each time the loop ends, then set that as a key for each rendered component.
-
If you are loading your todos from api, set an id for each todo and use it as your component key.
-
Use a random number generator to set a unique key on each of your todos.
The best approaches are # 2 and # 3, I see that in your case you are trying to do # 2 (setting the key using id todo) but I think it is undefined, check it out.
Another solution is to use a uuid for each displayed component / todo.
You can install node-uuid for this .
Run: npm i --save node-uuid
Then import on your file: import uuid from 'node-uuid'
orconst uuid = require('node-uuid')
Now change your code like this:
return TodoAPI.filterTodos(todos, showCompleted, searchText).map((todo) => {
return (
//add unique key prop to keep track of individual components
<Todo key={uuid()} {...todo} />
);
});
And then you will be fine.
source to share
You can add a parameter index
to your function map
and then pass that index to your component Todo
:
export var TodoList = React.createClass ({
render: function() {
var { todos, showCompleted, searchText } = this.props;
var renderTodos = () => {
if(todos.length === 0) {
return (
<p className="container__message">No tasks</p>
);
}
return TodoAPI.filterTodos(todos, showCompleted, searchText).map((todo, index) => { // here <====
return (
//add unique key prop to keep track of individual components
<Todo key={`key-${index}`} {...todo} />
);
});
};
return (
<div>
{renderTodos()}
</div>
);
}
});
source to share