1

I have an object that contains array of arrays. I was able to make it render the way I wanted. However, the React key is giving me trouble; throwing this "Warning: Each child in a list should have a unique "key" prop."

I have tried giving a unique 'id' property within the original array object for each element in each array but did not help.

I also looked through these but I think I have all of the suggestions there: Each child in an array or iterator should have a unique "key" prop in reactjs Warning: Each child in a list should have a unique "key" prop Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `ListView`

My object

const courses = 
[
    {
      name: 'Half Stack application development',
      id: 'Half Stack application development',
      parts: [
        {
          name: 'Fundamentals of React',
          exercises: 10,
          id: 'Fundamentals of React'
        },
        {
          name: 'Using props to pass data',
          exercises: 7,
          id: 'Using props to pass data'
        },
        {
          name: 'State of a component',
          exercises: 14,
          id: 'State of a component'
        },
        {
          name: 'Redux',
          exercises: 11,
          id: 'Redux'
        }
      ]
    }, 
    {
      name: 'Node.js',
      id: 'Node.js',
      parts: [
        {
          name: 'Routing',
          exercises: 3,
          id: 'Routing'
        },
        {
          name: 'Middlewares',
          exercises: 7,
          id: 'Middlewares'
        }
        ]
    }
]


ReactDOM.render(<App course={courses}/>, document.getElementById('root'));

This is the callback order:

App -> Courses -> Course -> {Header, Content -> Part}

const App = ({course}) => {
  return (
    <>
      <Courses course={course} />
    </>
  )
}

const Courses = ({course}) => {
    console.log("Starting.....")
    const courseList = course.map(nameConent => nameConent)
    // console.log(courseList)
    // const idList = courseList.map(eachCourse =>eachCourse.id)
    const mapEverything = () => courseList.map(a => <Course name={a.name} partsList={a.parts} id={a.id}/>)
    // const mapEverything = () => courseList.map(a => console.log(a.id))
    // console.log("CourseID",idList)
    return (
        <>  
            {mapEverything()}
        </>

    )
}

const Course = ({name,partsList,id}) => {
    // console.log(name)
    console.log("CourseID", id)

    return (
        <>
            <div key={id}>
                <Header header={name} id={id}/>
                <Content contents={partsList} id={id+"======"}/>
            </div>
        </>
    )
}

const Content = ({contents,id}) => {
    console.log("contentsID",id)
    const partList = () => contents.map(part =>
        <Part
            name={part.name}
            id={part.id} 
            exercises={part.exercises}
        />
    )

    const getSumofArray = (accumulator, currentValue) => accumulator + currentValue

    const exeList = contents.map(content => content.exercises).reduce(getSumofArray)
    // console.log(exeList)
    return (
        <>
            <ul key={id}>
                {partList()}
            </ul>
            <b>total exercises: {exeList}</b>
        </>
    )
}

const Header = ({header, id}) => {
    console.log("HeaderID", id)
    return (
        <>
            <h1 key={id}>
                {header}
            </h1>
        </>
    )
}

const Part = ({name, id, exercises}) => {
    console.log("PartID", id)
    return (
        <>
            <li  key={id}>
                {name}: {exercises}
            </li>
        </>
    )
}


It's having trouble at the Courses and Content component.

screenshot of warnings: https://drive.google.com/open?id=1kcLlF0d90BG6LXPojDtfSlBFGafC9HC_

2 Answers2

2

I think a problem is here:

const mapEverything = () => courseList.map(a => <Course name={a.name} partsList={a.parts} id={a.id}/>)

You should try add key here:

const mapEverything = () => courseList.map(a => <Course name={a.name} partsList={a.parts} id={a.id} key={a.id}/>)

And here too:

const partList = () => contents.map(part =>
        <Part
            name={part.name}
            id={part.id} 
            exercises={part.exercises}
            key={part.id}
        />)

Here is a good explanation of why you need to do it this way.

Noob
  • 2,247
  • 4
  • 20
  • 31
  • you are correct but explain a bit why we need `key`. – ravibagul91 Sep 08 '19 at 01:53
  • 1
    Keys help React know which elements are the same or have changed to reuse (or create new) elements. You can read more or see more examples in the docs: https://reactjs.org/docs/lists-and-keys.html – skovy Sep 08 '19 at 01:54
  • wait, so the key is not for referencing rendered HTML but sort of internal for React? I read the reactjs doc several time and went away with the impression that it needs to be part of the rendered DOM although hidden. Also, if you look at my screenshot, all of the values for Key were able to passed to each of the callback components, why do I need to explicitly pass another 'Key' component? – Timothy Cumberland Sep 08 '19 at 02:19
  • @TimothyCumberland yes , you are right it's needed for internal functioning of React. – Noob Sep 08 '19 at 02:21
0

This is the topic of Key Props. Whenever we render an array or list in Reactjs, it expects to have a key property available.

The presence of this key property is for performance concerns, it makes it easier for Reactjs to render a list of items.

All you have to do is provide another prop to any other elements in the array. The most unique identifier you have in your course list seems to be name. So you may want to simplify your code to something like this to get yourself going:

const CourseList = () => {
  const RenderedCourses = courses.map(function(course) {
    return <CourseDetail key={course.name} course={course} />;
  });
  return <ul>{RenderedCourses}</ul>;
};

Then once you got that working you can make the code concise how you had it with arrow function and all:

const CourseList = () => {
  const RenderedCourses = courses.map(course => (
    <CourseDetail key={course.name} course={course} />
  ));
  return <ul>{RenderedCourses}</ul>;
};
Daniel
  • 14,004
  • 16
  • 96
  • 156