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'
};

      

+3


source to share


4 answers


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?

+3


source


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.

+3


source


I bet todo.id

undefined

and therefore am not unique. Can you include todos

in your example?

+2


source


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>
        );  
    }
});

      

+2


source







All Articles