DaleZA
DaleZA

Reputation: 191

Each Child in a list should have a unique "key" prop but I have added key

I cannot get rid of the "Each child on a list should have a unique "key" prop error" I've tried using Math.random() to make sure my iteration isn't causing duplicate keys as well as adding the key to list components like Title and Image. I've also reviewed the reacts documentation but I don't see anything similar to this.

 {menuCategory.map((category, index) => (
      <React.Fragment key={index}>
        {index != 0 ? (
          <ListItem
            key={category.id}
            topDivider
            titleStyle={themestyles.CardText}
            containerStyle={themestyles.ListItemDivider}>
            {console.log(category.category_data.name + category.id)}
            <ListItem.Content containerStyle={themestyles.ListItemContent}>
              <ListItem.Title style={themestyles.ItemTitle}>
                {category.category_data.name}
              </ListItem.Title>
            </ListItem.Content>
          </ListItem>
        ) : (
          <ListItem
            key={category.id}
            topDivider
            titleStyle={themestyles.CardText}
            containerStyle={themestyles.ListItemDividerTop}>
            {console.log(category.category_data.name + category.id)}
            <ListItem.Content containerStyle={themestyles.ListItemContent}>
              <ListItem.Title style={themestyles.ItemTitle}>
                {category.category_data.name}
              </ListItem.Title>
            </ListItem.Content>
          </ListItem>
        )}
        {menuItem.map((item) =>
          category.id == item.item_data.category_id ? (
            <Card
              key={item.id}
              titleStyle={themestyles.Card}
              containerStyle={themestyles.Card}
              featuredSubtitle={item.item_data.description}
              title={item.item_data.name}>
              {console.log(item.item_data.name + ' ' + item.id)}
              <React.Fragment key={2+Math.random()}>
                {console.log(
                  'Image:: ' + menuImage[item.item_data.image_ids],
                )}
              </React.Fragment>
              <Card.Title>{item.item_data.name}</Card.Title>
              {menuImage[item.item_data.image_ids] != undefined ? (
                <Card.Image
                  resizeMode="contain"
                  source={{
                    uri: `${menuImage[item.item_data.image_ids]}`,
                  }}
                />
              ) : (
                <Card.Image
                  resizeMode="contain"
                  source={require('../../creative/icon.png')}
                />
              )}
            </Card>
          ) : (
            <></>
          ),
        )}
      </React.Fragment>
    ))}

Upvotes: 0

Views: 172

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370859

The problem is with the empty fragment at the very end:

    {menuItem.map((item) =>
      category.id == item.item_data.category_id ? (
        <Card
          key={item.id}
          ...
        </Card>
      ) : (
        <></>
        ^^^^^^^^^^^^^^^^^^^^^^^
      ),
    )}

That's returned from the .map and lacks a key.

For a more minimal example of the same problem:

const App = () => {
    return [0, 1, 2, 3, 4].map(
      num => num % 2 === 0
        ? <div>{num}</div>
        : <React.Fragment></React.Fragment>
    );
};

ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>

While you could fix it by adding a key to the fragment - by chaning <></> to <React.Fragment key={item.item_data.category_id}></React.Fragment> - a better approach would be to filter out items that don't match so that you don't have to write JSX markup for it at all. Instead of

{menuItem.map((item) =>
  category.id == item.item_data.category_id ? (
    <Card

do

{menuItem
  .filter(item => category.id == item.item_data.category_id)
  .map(item =>
    <Card

You should also consider using strict equality (===) instead of sloppy equality (==), and also remove the fragment here:

          {console.log(item.item_data.name + ' ' + item.id)}
          <React.Fragment key={2+Math.random()}>
            {console.log(
              'Image:: ' + menuImage[item.item_data.image_ids],
            )}
          </React.Fragment>

Don't use Math.random for keys - and there's no need for a fragment here at all, because your only goal is to log inside the JSX. Log in the line above instead (where you're already logging the item's name and ID) - follow that same pattern to log again instead of creating a superfluous fragment. One option would be to use an IIFE that doesn't return anything.

  title={item.item_data.name}
>
{(() => {
  console.log(item.item_data.name + ' ' + item.id);
  console.log(
    'Image:: ' + menuImage[item.item_data.image_ids],
  );
})()}

Or you could log at the beginning of the .map callback, which would look more natural.

menuItem.map((item) => {
  console.log(item.item_data.name + ' ' + item.id);
  console.log(
    'Image:: ' + menuImage[item.item_data.image_ids],
  );
  return menuItem.filter(
    // ...

Upvotes: 4

Related Questions