Laura Silvani
Laura Silvani

Reputation: 757

React: flattenChildren(...): Encountered two children with the same key

I know this question has been asked before but I am not sure I understand what's happening here. I have an array of objects that I am mapping in a React component to create a navigation: some element could have children, so I am adding a second .map function for those elements. Everything works fine but the second child element is not displayed because it "encounters two children with the same key". Confused...could anyone explain why? I am using the title as key which is now unique.

Here is my items array

let navItems = [
  {
    title: "Home",
    href: "/",
    children: []
  },
  {
    title: "Evaluations",
    href: "/evaluations",
    children: [
      {
       id: 1,
       title: "List all evalutions",
       href: "/"
      },
      {
        id: 2,
        title: "Planner",
        href: "/"
      },
    ]
  }
]

And here is the code:

let items = this.props.items.map((item) => {
  if(item.children.length > 0) {
    return item.children.map((child) => {
      return <li className="menu--item" key={ item.title }>
        <a href={ item.href } className="menu--link">{ item.title }</a>
        <ul className="dropdown">
          <li key={ child.title }><a href="{ child.href}">{ child.title }</a></li>
        </ul>
      </li>
    })
  } else  {
    return <li className="menu--item" key={ item.title }>
      <a href={ item.href } className="menu--link">{ item.title }</a>
    </li>
  }
})

Upvotes: 0

Views: 630

Answers (2)

DonovanM
DonovanM

Reputation: 1198

This is happening because you are mapping over item.children but returning a key with item.title inside. item.title doesn't change within that loop so you end up with 2 <li> elements with a key of "Evaluations". I suspect that you would want to use map inside <ul className="dropdown">.

let items = this.props.items.map((item) => {
  if(item.children.length > 0) {
      return <li className="menu--item" key={ item.title }>
        <a href={ item.href } className="menu--link">{ item.title }</a>
        <ul className="dropdown">
          {item.children.map((child) => {
            return <li key={ child.title }><a href="{ child.href}">{ child.title }</a></li>
          })}
        </ul>
      </li>
  } else  {
    return <li className="menu--item" key={ item.title }>
      <a href={ item.href } className="menu--link">{ item.title }</a>
    </li>
  }
})

Upvotes: 2

Ritesh Bansal
Ritesh Bansal

Reputation: 3238

You should provide a unique key to each element while you are rendering items in an iteration.

Here, you are using item.title as the key for the children of the item which is same for each child of that item and you are returning an array if item.children.length > 0 and returning an element in else case. I think you should write it like this:

let items = this.props.items.map((item) => {
  if(item.children.length > 0) {
    return (
      <div key={item.id}>
        {item.children.map((child) => {
          return (
            <li className="menu--item" key={ child.id }>
              <a href={ item.href } className="menu--link">{ item.title }</a>
              <ul className="dropdown">
                <li key={ child.title }><a href="{ child.href}">{ child.title }</a></li>
              </ul>
            </li>  
          )
        })}
      </div>
    )
  } else  {
    return <li className="menu--item" key={ item.id }>
      <a href={ item.href } className="menu--link">{ item.title }</a>
    </li>
  }
})

and there should be an unique id in each navLink like this:

let navItems = [
  {
    id: '1',
    title: "Home",
    href: "/",
    children: []
  },
  {
    id: '2',
    title: "Evaluations",
    href: "/evaluations",
    children: [
      {
       id: '2.1',
       title: "List all evalutions",
       href: "/"
      },
      {
        id: '2.2',
        title: "Planner",
        href: "/"
      },
    ]
  }
]

Hope, it helps.

Upvotes: 0

Related Questions