渲染过程中出现以下错误/警告:
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)
该怎么办?我是否需要向ListItem
material-ui
组件添加uniq键?
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>
);
}
}
答案 0 :(得分:2)
您应该在渲染内部.map
内的组件中添加唯一的道具
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>
);
});
请注意,使用i
(索引)不好,您应该拥有一个独特的属性,例如id
。
答案 1 :(得分:1)
您的问题在以下循环中
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>
);
})
每个列表项应具有唯一的键(在同级中),因此您需要像这样为内部和外部循环提供键
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>
);
});
在此示例中,我使用index
作为键,但是您应该使用avoid that
答案 2 :(得分:1)
是的,您需要为每个ListItem
提供一个唯一的密钥,例如id
。您可以使用Array.map()
中的索引,但是通常不建议这样做。
如官方React documentation所述,
键可帮助React识别哪些项目已更改,添加或为 删除。键应赋予数组内的元素以给出 元素具有稳定的身份:
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>
)
});
答案 3 :(得分:0)
将 ListItem 组件封装在
<React.Fragment key={`some-unique-id`}>
<ListItem >
...
</ListItem>
</React.Fragment>