I'm going through a tutorial for todo list in React and ran into the following error, I have spent quite a time and just can't find the mistake..here is the error and the code for the component and this is the code for the course repo(problem appears on this commit):
https://github.com/andrewjmead/react-course-todo-app/commit/0521f151705f78cb9f8d69262eb093f1431cb9ca
Any help much appreciated.
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of TodoList
. See fb.me/react-warning-keys for more information.
Also there is an error in terminal, for the spread operator in case 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);
Reducers:
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'
};
In React, when you are rendering multiple equal components (in your case, the todos) you need to add a unique key to each one of them, that's because React needs to know how they are going to be treated in the virtual dom.
You can do various things to fix this:
In the for loop, create an index variable and increment it by 1 each time a loop finishes, then set that as the key for each rendered component.
If you're fetching your todos from an api, set an id to 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 the #2 and #3, I see that in your case you're trying to do the #2 (setting the key by todo id) but I think it's undefined, check it.
Another solution is to use a uuid on each rendered component/todo.
To do that, you can install node-uuid.
Run: npm i --save node-uuid
Then do the import in your file: import uuid from 'node-uuid'
or const uuid = require('node-uuid')
Now change your code to be 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're good to go.
node-uuid is deprecated, check here: https://www.npmjs.com/package/uuid
You can update your package.json by installing uuid and see if it helps:
npm install uuid
Just don't forget to update var uuid = require('node-uuid'); to var uuid = require('uuid'); in your other files.
P.S. Do you get any errors in your terminal when you run a webpack?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With