Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React + Material-UI: list should have a unique "key" prop

I get following error / warning during rendering:

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

Check the render method of `App`. See .. for more information.
    in ListItemCustom (at App.js:137)
    in App (created by WithStyles(App))
    in WithStyles(App) (at src/index.js:7)

What to do? Do I need to add a uniq key to my ListItem material-ui component?

App.js:

import React, { Component } from "react";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
import FacebookLogin from "react-facebook-login";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import ArrowForwardIos from "@material-ui/icons/ArrowForwardIos";
import ArrowBackIos from "@material-ui/icons/ArrowBackIos";
import axios from "axios";
import ListItemCustom from "./components/ListItemCustom";
import ListSubheader from "@material-ui/core/ListSubheader";
import Switch from "@material-ui/core/Switch";
import TextField from "@material-ui/core/TextField";
import Box from "@material-ui/core/Box";
import IconButton from "@material-ui/core/IconButton";

// import this
import { withStyles } from "@material-ui/core/styles";

// make this
const styles = theme => ({
  root: {
    flexGrow: 1
  },
  menuButton: {
    marginRight: theme.spacing(2)
  },
  title: {
    flexGrow: 1
  },
  listSubHeaderRoot: {
    backgroundColor: "#E5E5E5",
    color: "#252525",
    lineHeight: "22px"
  }
});

class App extends Component {
  state = {
    accessToken: "",
    isLoggedIn: false,
    userID: "",
    name: "",
    email: "",
    picture: "",
    selectedEvent: undefined,
    buyOrRelease: "buy",
    pages: []
  };

  responseFacebook = response => {
    this.setState({
      accessToken: response.accessToken,
      isLoggedIn: true,
      userID: response.userID,
      name: response.name,
      email: response.email,
      picture: response.picture.data.url
    });
    let accessToken = response.accessToken;
    axios
      .get(
        "https://graph.facebook.com/v5.0/me/accounts?fields=id,name&access_token=" +
          response.accessToken
      )
      .then(async pagesResponse => {
        let promisesArray = pagesResponse.data.data.map(async page => {
          console.log("page " + page.id + " " + page.name);
          return axios
            .get(
              "https://graph.facebook.com/v5.0/" +
                page.id +
                "/events?fields=id,name&access_token=" +
                accessToken
            )
            .catch(e => e);
        });
        const responses = await Promise.all(promisesArray);
        var pages = [];
        responses.forEach((response, i) => {
          const page = pagesResponse.data.data[i];
          pages.push({
            id: page.id,
            name: page.name,
            events: response.data.data
          });
        });
        this.setState({
          pages: pages
        });
      });
  };

  handleClick = event =>
    this.setState({
      anchorEl: event.currentTarget
    });
  handleClose = () => {
    this.setState({ anchorEl: undefined });
  };
  handleCloseAndLogOut = () => {
    this.setState({ anchorEl: undefined });
    this.setState({ isLoggedIn: undefined });
    this.setState({ userID: undefined });
    this.setState({ name: undefined });
    this.setState({ email: undefined });
    this.setState({ picture: undefined });
  };

  switchToRelease = () => {
    this.setState({ buyOrRelease: "release" });
  };

  switchToBuy = () => {
    this.setState({ buyOrRelease: "buy" });
  };

  componentDidMount() {
    document.title = "Tiket.hu";
  }

  handleSort = event => {
    this.setState({ selectedEvent: event });
  };

  navigateBack = () => {
    this.setState({ selectedEvent: undefined });
  };

  render() {
    let fbOrMenuContent;
    let listContent;
    let buyOrReleaseMenuItem;
    if (this.state.isLoggedIn) {
      let eventsList;
      if (this.state.buyOrRelease === "buy") {
      } else {
        eventsList = this.state.pages.map(page => {
          let eventsList2 = page.events.map(event => (
            <ListItemCustom key={event.id} value={event} onHeaderClick={this.handleSort} />
          ));
          return (
            <div>
              <ListSubheader className={this.props.classes.listSubHeaderRoot} key={page.id}>{page.name}</ListSubheader>
              {eventsList2}
            </div>
          );
        });
      }
      listContent = (
        <div>
          <List component="nav" aria-label="main mailbox folders">
            {eventsList}
          </List>
        </div>
      );
      if (this.state.selectedEvent) {
        listContent = (
          <div>
            <List component="nav" aria-label="main mailbox folders">
              <ListItem button onClick={this.navigateBack}>
                <IconButton edge="start" aria-label="delete">
                  <ArrowBackIos />
                </IconButton>

                <Box textAlign="left" style={{ width: 150 }}>
                  Back
                </Box>
                <ListItemText
                  secondaryTypographyProps={{ align: "center" }}
                  primary={this.state.selectedEvent.name}
                />
              </ListItem>

              <ListItem button>
                <Box textAlign="left" style={{ width: 150 }}>
                  Select auditorium
                </Box>
                <ListItemText
                  secondaryTypographyProps={{ align: "right" }}
                  secondary="UP Újpesti Rendezvénytér"
                />
                <IconButton edge="end" aria-label="delete">
                  <ArrowForwardIos />
                </IconButton>
              </ListItem>

              <ListItem button>
                <Box textAlign="left" style={{ width: 150 }}>
                  Release purpose
                </Box>
                <ListItemText
                  secondaryTypographyProps={{ align: "right" }}
                  secondary="Normal selling"
                />
                <IconButton edge="end" aria-label="delete">
                  <ArrowForwardIos />
                </IconButton>
              </ListItem>

              <ListItem>
                <ListItemText primary="Start selling" />
                <Switch edge="end" />
              </ListItem>

              <ListItem>
                <ListItemText primary="Notify if different price would increase revenue" />
                <Switch edge="end" />
              </ListItem>

              <ListSubheader className={this.props.classes.listSubHeaderRoot}>
                Sector
              </ListSubheader>

              <ListItem button>
                <Box textAlign="left" style={{ width: 150 }}>
                  Select sector
                </Box>
                <ListItemText
                  secondaryTypographyProps={{ align: "right" }}
                  secondary="A"
                />
                <IconButton edge="end" aria-label="delete">
                  <ArrowForwardIos />
                </IconButton>
              </ListItem>

              <ListItem button>
                <Box textAlign="left" style={{ width: 500 }}>
                  Marketing resource configuration & result
                </Box>
                <ListItemText
                  secondaryTypographyProps={{ align: "right" }}
                  secondary=""
                />
                <IconButton edge="end" aria-label="delete">
                  <ArrowForwardIos />
                </IconButton>
              </ListItem>

              <ListItem>
                <ListItemText primary="Price in sector" />
                <TextField InputLabelProps={{ shrink: true }} />
              </ListItem>
            </List>
          </div>
        );
      }
      if (this.state.buyOrRelease === "buy") {
        buyOrReleaseMenuItem = (
          <Menu
            id="simple-menu"

            anchorEl={this.state.anchorEl}
            keepMounted
            open={Boolean(this.state.anchorEl)}
            onClose={this.handleClose}
          >
            <MenuItem onClick={this.handleCloseAndLogOut}>Log out</MenuItem>
            <MenuItem onClick={this.switchToRelease}>
              Switch Release mode
            </MenuItem>
            <MenuItem onClick={this.handleClose}>My tickets</MenuItem>
          </Menu>
        );
      } else {
        buyOrReleaseMenuItem = (
          <Menu
            id="simple-menu"
            anchorEl={this.state.anchorEl}
            keepMounted
            open={Boolean(this.state.anchorEl)}
            onClose={this.handleClose}
          >
            <MenuItem onClick={this.handleCloseAndLogOut}>Log out</MenuItem>
            <MenuItem onClick={this.switchToBuy}>Switch Buy mode</MenuItem>
          </Menu>
        );
      }
      fbOrMenuContent = (
        <div>
          <Button
            aria-controls="simple-menu"
            aria-haspopup="true"
            onClick={this.handleClick}
          >
            {this.state.name}
          </Button>
          {buyOrReleaseMenuItem}
        </div>
      );
    } else {
      let fbAppId;
      if (
        window.location.hostname === "localhost" ||
        window.location.hostname === "127.0.0.1"
      )
        fbAppId = "402670860613108";
      else fbAppId = "2526636684068727";
      fbOrMenuContent = (
        <FacebookLogin
          appId={fbAppId}
          autoLoad={true}
          fields="name,email,picture"
          scope="public_profile,pages_show_list"
          onClick={this.componentClicked}
          callback={this.responseFacebook}
        />
      );
    }
    return (
      <div className="App">
        <AppBar position="static">
          <Toolbar>
            <Typography variant="h6" className={this.props.classes.title}>
              Tiket.hu
            </Typography>
            <Button color="inherit">Search</Button>
            <Button color="inherit">Basket</Button>
            {fbOrMenuContent}
          </Toolbar>
        </AppBar>
        {listContent}
      </div>
    );
  }
}

export default withStyles(styles)(App);

ListItemCustom.js:

import React, { Component } from "react";
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import ArrowForwardIos from "@material-ui/icons/ArrowForwardIos";

export default class ListItemCustom extends Component {
  eventSelected = () => {
    this.props.onHeaderClick(this.props.value);
  };
  render() {
    return (
      <ListItem button key={this.props.value.id} onClick={this.eventSelected}>
        <ListItemText primary={this.props.value.name}/>
        <ListItemIcon>
          <ArrowForwardIos />
        </ListItemIcon>
      </ListItem>
    );
  }
}

like image 506
János Avatar asked Dec 03 '19 16:12

János


People also ask

How do you fix each child in a list should have a unique key prop?

The Solution. When creating a list in the UI from an array with JSX, you should add a key prop to each child and to any of its' children. React uses the key prop create a relationship between the component and the DOM element.

Why each child in a list should have a unique key prop?

⚠️ Warning: Each child in a list should have a unique “key” prop. This is because React uses a unique “key” prop on each child of the list to create a relationship between the component and the DOM. This is to ensure that react re-renders the child correctly next time.

Do React keys need to be globally unique?

Unstable keys can crash the whole app. Finally, we should note that keys need only be unique among adjacent elements or sibling list items. A key does not have to be globally unique, but only within the scope of a list.

Why does React list need key?

Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity: const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.


1 Answers

You should add an unique prop to your components inside .map that is inside your render

eventsList = this.state.pages.map(page => {     
    let eventsList2 = page.events.map((event, i) => (
            //       unique key prop
            <ListItemCustom key={i} value={event} onHeaderClick={this.handleSort} />
          ));
          return (
            <div key={page.name}> // unique key prop
              <ListSubheader>{page.name}</ListSubheader>
              {eventsList2}
            </div>
          );
        });

Please notice that using i (the index) isn't good, you should have an unique property like an id.

like image 115
Vencovsky Avatar answered Oct 13 '22 02:10

Vencovsky