2

My component is supposed to retrieve the data for courses when the component mounts. The problem that I have is that whether I use the course Id or the course title as the key, I get the following error:

index.js:1 Warning: Each child in a list should have a unique "key" prop.

I have looked through the react docs, here on Stack Overflow, and tried different ways to get it to work. The only way I can get it to partially work is by adding an index as a parameter for map. When I use this method, I run into another problem and that is, it stops after the first iteration, even though there are 10 items. How can I fix this?

Here is my code:

CoursesPage.js

import React from 'react';
import { connect } from 'react-redux';
import * as courseActions from '../../redux/actions/courseActions';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';

class CoursesPage extends React.Component {

    componentDidMount() {
        this.props.actions.loadCourses().catch(error => {
            alert("Loading courses failed" + error);
        });

    }


    render() {
        return (
            <>
                <h2>Courses</h2>

                

{this.props.courses.map((course, index) => (

                    <div key={course[index].title}>{course[index].title}</div>


                ))}
            </>
        );
    }
}

CoursesPage.propTypes = {
    courses: PropTypes.array.isRequired,
    actions: PropTypes.object.isRequired
}

function mapStateToProps(state) {
    return {
        courses: state.courses
    }

}

function mapDispatchToProps(dispatch) {

    return {
        actions: bindActionCreators(courseActions, dispatch)
    }

}

export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage);

My mock data:

const courses = [
    {
        id: 1,
        title: "Securing React Apps with Auth0",
        slug: "react-auth0-authentication-security",
        authorId: 1,
        category: "JavaScript"
    },
    {
        id: 2,
        title: "React: The Big Picture",
        slug: "react-big-picture",
        authorId: 1,
        category: "JavaScript"
    },
    {
        id: 3,
        title: "Creating Reusable React Components",
        slug: "react-creating-reusable-components",
        authorId: 1,
        category: "JavaScript"
    },
    {
        id: 4,
        title: "Building a JavaScript Development Environment",
        slug: "javascript-development-environment",
        authorId: 1,
        category: "JavaScript"
    },
    {
        id: 5,
        title: "Building Applications with React and Redux",
        slug: "react-redux-react-router-es6",
        authorId: 1,
        category: "JavaScript"
    },
    {
        id: 6,
        title: "Building Applications in React and Flux",
        slug: "react-flux-building-applications",
        authorId: 1,
        category: "JavaScript"
    },
    {
        id: 7,
        title: "Clean Code: Writing Code for Humans",
        slug: "writing-clean-code-humans",
        authorId: 1,
        category: "Software Practices"
    },
    {
        id: 8,
        title: "Architecture Applications for the Real World",
        slug: "architecting-applications-dotnet",
        authorId: 1,
        category: "Software Architecture"
    },
    {
        id: 9,
        title: "Becoming an Outlier: Reprogramming the Developer Mind",
        slug: "career-reboot-for-developer-mind",
        authorId: 1,
        category: "Career"
    },
    {
        id: 10,
        title: "Web Component Fundamentals",
        slug: "web-components-shadow-dom",
        authorId: 1,
        category: "HTML5"
    }
];

const authors = [
    { id: 1, name: "Cory House" },
    { id: 2, name: "Scott Allen" },
    { id: 3, name: "Dan Wahlin" }
];

const newCourse = {
    id: null,
    title: "",
    authorId: null,
    category: ""
};

module.exports = {
    newCourse,
    courses,
    authors
};

Edit: I am using Redux Thunk.

Here is my actionType.js file:

export const CREATE_COURSE = "CREATE_COURSE";
export const LOAD_COURSES_SUCCESS = "LOAD_COURSES_SUCCESS";

Here is my CourseActions.js file:

import * as types from './actionTypes';
import * as courseApi from "../../api/courseApi";

export function createCourse(course) {
    return { type: types.CREATE_COURSE, course };
}

export function loadCourseSuccess(courses) {
    return { type: types.LOAD_COURSES_SUCCESS, courses };
}

export function loadCourses() {
    return function (dispatch) {
        return courseApi.getCourses().then(courses => {
            dispatch(loadCourseSuccess(courses));
        }).catch(error => {
            throw error;
        })

    }
}

Here is my courseReducer.js file:

import * as types from '../actions/actionTypes';

export default function courseReducer(state = [], action) {

    switch (action.type) {
        case types.CREATE_COURSE:
            return [...state, { ...action.course }];

        case types.LOAD_COURSES_SUCCESS:
            return [...state, { ...action.courses }];

        default:
            return state;
    }
}

Any help would be appreciated.

Thanks.

P.S. I know that you should use Id for key. But the way it has to be done for now is using the title of the course as the key.

6
  • 1
    you do not need to access course[index] when inside a map. course already contains the value pertaining to the current index. just do course.title Commented Aug 27, 2020 at 5:02
  • Hi, I get the same error that I mentioned at the top of my post: index.js:1 Warning: Each child in a list should have a unique "key" prop. Commented Aug 27, 2020 at 5:06
  • you don t need index to iterate the array. That method is used only if you map array in array and need to check some conditions.. @MA_Dev give the right anser. Try yo put also key={index} Commented Aug 27, 2020 at 5:09
  • 1
    looks ok: codesandbox.io/s/infallible-germain-q6cze?file=/src/App.js Commented Aug 27, 2020 at 5:22
  • It fetches fine. Its just outputting to the page that is the problem. I have console.log(course) within map and it outputs it fine. Commented Aug 27, 2020 at 5:26

5 Answers 5

1

With your edits, I think we can more effectively help you. For future, it would be beneficial to post an example of your code not working on https://codesandbox.io/

Also to help yourself out when you debug, isolate the react component from the use of redux. This will allow you to ensure your react component renders when given data, then focus on getting redux to provide you the data your react component has.

You can do this by first defining mockdata inside your react component, then moving that mock data to your reducer, then finally replacing the mock data with the live api call.

On to the code:

You have two issues: the first is that you want to index into the array courses but instead due to a typo, you are actually using the property accessor into the object course

key={course[index].title}

As your question states you must use the title as the key simply change the div to be:

<div key={course.title}>{course.title}</div> and your code should work as expected.

Once you have addressed that, then re-enable loading data from your API call using Redux, and you can address the issues with Redux.

Looking at your reducer, you have a clear bug, and depending on your use case, a potential bug:

case types.LOAD_COURSES_SUCCESS:
            return [...state, { ...action.courses }];

action.courses is an array, and the code is creating a new array that contains all the elements of the previous array state and adding a new object, which contains the contents of a destructured array.courses array.

Which that does is effectively append a single object to your array, and the object is comprised of elements from your courses array. The item index becomes the key, and the item itself is the value.

You can visualize it here: https://codesandbox.io/s/divine-pond-nr3j8

Instead you want

return [...state, ...action.courses];

The second, potential bug, is that you are actually appending the results of the courses api. Which for subsequent calls to load courses, you will duplicate the data. That may be what you want, but I am going to assume that is not what you want.

So instead, your LOAD_COURSES_SUCCESS case should be rewritten to simply be:

return [...action.courses];

Sign up to request clarification or add additional context in comments.

2 Comments

Same error: Warning: Each child in a list should have a unique "key" prop.
Hi Alan, Thank you for your advice and help. The problem was in the reducer as you correctly pointed out. When I tried return [...action.courses]; it worked, no errors appeared in the console and the courses loaded on the page. Thanks to everyone who helped me. I appreciate all of your help.
0

Since each course has a unique ID field, the problem could be solved by using the id as a key

render() {
  return (
    <>
      <h2>Courses</h2>



      {this.props.courses.map(course => (

        <div key={course.id}>{course.title}</div>


      ))}
    </>
  );
}

3 Comments

Same error: Warning: Each child in a list should have a unique "key" prop.
Might be something wrong with the courses array, then. Have you tried to print courses? Something like {JSON.stringify(this.props.courses)} right in JSX. This will shed some light on the problem.
Yes. It printed out fine using JSON.stringify(this.props.courses). It appears on the page. I also printed it out in the browser console and there wasn't any issue.
0

The issue is here:

{this.props.courses.map((course, index) => (

                    <div key={course[index].title}>{course[index].title}</div>


                ))}
//course in itself is data

Solution:

{this.props.courses.map((course, index) => (

                    <div key={`${course.title}-${index}`}>{course.title}</div>


                ))}

Better way would always to take care unique id for key

4 Comments

Hi, I am still getting the same error: Warning: Each child in a list should have a unique "key" prop.
try this: ${course.title}-${index} . Updated my answer
Now I am not getting any errors, but no data is being loaded on the page.
I can't see any error. Can you cross check or update this sandbox? codesandbox.io/s/quizzical-sun-trvki
0

Here's the code snippet for render method. Give it a try.

*Update = use course title as key

render() {
    const courseList = this.props.courses.map(course => (
        <div key={course.title}></div>
    ));
        
    return (
        <>
            <h2>Courses</h2>
            <div>{courseList}</div>
        </>
    );
}
                

1 Comment

Same error: Warning: Each child in a list should have a unique "key" prop.
0

There are many ways you can pass the key when it is iterating...

var courses = this.state.courses.map(function(course, index) {
        return(
            <div key={index}>
                <div key={course.title} id={course.title}>
                    <h2 key={"header"+course.title}>{course.title}</h2>
                </div>
            </div>
        )
    });

But you should try to pass a unique id rather than a string.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.