Reputation: 35090
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>
);
}
}
Upvotes: 14
Views: 24232
Reputation: 529
To get rid of this warning whatever component is giving you this warning you need to place the key props to its immediate child.
for eg:
<List ...>
<ListItem key={applyUniqueValueHere}></ListItem>
</List>
OR
<Menu ...>
<ToolTip key={applyUniqueValueHere}></ToolTip>
</Menu>
OR
<List ...>
<>...</>
//This wont allow to provide key props so convert it to
React.Fragment
</List>
Like this
<List ...>
<React.Fragment key={uniqueIdGoesHere}>...</React.Fragment>
</List>
Upvotes: 0
Reputation: 11
Inside the map, wrap your element with a div like that:
<div key={i}> <ListItemCustom value={event} onHeaderClick={this.handleSort} /> </div>
Note: It should work but you need to avoid using the index as a key
Upvotes: 1
Reputation: 4224
Enclose the ListItem component inside <React.Fragment> and apply key property to the <React.Fragment> component as follows:
<React.Fragment key={`some-unique-id`}>
<ListItem >
...
</ListItem>
</React.Fragment>
Upvotes: 4
Reputation: 42556
You are right, you will need to supply each ListItem
with a unique key, such as an id
. You may use the index from Array.map()
, but it is generally not recommended.
As stated on the official React documentation,
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:
eventsList = this.state.pages.map((page) => {
let eventsList2 = page.events.map((event) => (
<ListItemCustom value={event} onHeaderClick={this.handleSort} />
));
return (
<div>
<ListSubheader key={page.id}>{page.name}</ListSubheader>
{eventsList2}
</div>
)
});
Upvotes: 1
Reputation: 21347
Your problem is in the following loop
eventsList = this.state.pages.map(page => {
let eventsList2 = page.events.map(event => (
<ListItemCustom value={event} onHeaderClick={this.handleSort} />
));
return (
<div>
<ListSubheader>{page.name}</ListSubheader>
{eventsList2}
</div>
);
})
Each list item should have an unique key (among siblings), so you need to provide keys for the inner and outer loop, like this
eventsList = this.state.pages.map((page,index) => {
let eventsList2 = page.events.map((event,i) => (
<ListItemCustom key={i} value={event} onHeaderClick={this.handleSort} />
));
return (
<div key={index}>
<ListSubheader>{page.name}</ListSubheader>
{eventsList2}
</div>
);
});
In this example I'm using the index
as key, but you should avoid that
Upvotes: 1
Reputation: 31683
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
.
Upvotes: 11