Reputation: 1796
I seem to be stuck trying to solve for unique key ID's for my list items. I thought I understood how unique keys work, but clearly I do not.
For my code below, having a key as key={index}
is not enough, same goes for key={item}
. I keep getting duplicate errors from my console or the fact that my children inside my list do not have key's.
Below is the component that is causing errors. I don't get why Warning: Encountered two children with the same key,
is appearing. Wouldn't the children inside a list item (based off the same mapping iteration) want to have the same keys?
For my specific case, I can't use item.id
as a key, that is not available for the data I'm using. I did try doing something like item.symbol
(Im presenting stock market data) and again this was causing duplicate key errors.
How can I solve this?
export const MarketMovement = (props) =>
{
const { financialData } = props;
const priceAvgChangeASC = financialData[2] && sortAll(financialData[2], 'asc', 'price_avg_change')
.filter((i) => i.price_avg_change < 0)
.slice(0, 4)
.map((stock, index) => (
<Grid>
<ListItem key={index}>
<ListItemAvatar>
<Avatar style={{background:GradientRankGreen[index]}}>
{index + 1}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={stock.symbol}
secondary={financialData && financialData[5].filter((i) => i.symbol === stock.symbol).map(
filteredSymbol => (
<>
{filteredSymbol.company}
</>
))}
/>
</ListItem>
</Grid>
));
const priceAvgChangeDESC = financialData[2] && sortAll(financialData[2], 'desc', 'price_avg_change')
.filter((i) => i.price_avg_change > 0)
.slice(0, 4)
.map((stock, index) => (
<Grid>
<ListItem key={index}>
<ListItemAvatar>
<Avatar style={{background:GradientRankRed[index]}}>
{index + 1}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={stock.symbol}
secondary={financialData && financialData[5].filter((i) => i.symbol === stock.symbol).map(
filteredSymbol => (
<>
{filteredSymbol.company}
</>
))}
/>
</ListItem>
</Grid>
));
const volumeAvgChangeASC = financialData[2] && sortAll(financialData[2], 'asc', 'volume_avg_change')
.filter((i) => i.volume_avg_change < -15)
.slice(0, 4)
.map((stock, index) => (
<Grid>
<ListItem key={index}>
<ListItemAvatar>
<Avatar style={{background: GradientPurple[index]}}>
{index + 1}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={stock.symbol}
secondary={financialData && financialData[5].filter((i) => i.symbol === stock.symbol).map(
filteredSymbol => (
<>
{filteredSymbol.company}
</>
))} />
</ListItem>
</Grid>
));
const volumeAvgChangeDESC = financialData[2] && sortAll(financialData[2], 'desc', 'volume_avg_change')
.filter((i) => i.volume_avg_change > 10)
.slice(0, 4)
.map((stock, index) => (
<Grid>
<ListItem key={index}>
<ListItemAvatar>
<Avatar style={{background:GradientOrange[index]}}>
{index + 1}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={stock.symbol}
secondary={financialData && financialData[5].filter((i) => i.symbol === stock.symbol).map(
filteredSymbol => (
<>
{filteredSymbol.company}
</>
))} />
</ListItem>
</Grid>
));
return (
<>
<Container style={{ padding: "10px", paddingBottom:"40px" }} maxWidth="md">
<Typography style={{paddingTop:"50px", paddingTop:"50px" }}variant="h2">Market Movers</Typography>
<Typography variant="overline">Stocks outpacing or declining in historical AVG's for price & volume</Typography>
<Grid justify="center" direction='row' container spacing={5} alignItems="stretch" >
<Grid item xs={12} s={12} md={6} lg={6}>
<List dense='true'>
<Typography variant="h5">Price {<Chip label="Bullish" size="small"/>}</Typography>
<Typography variant="caption">Average Closing Price</Typography>
<Divider component="li" variant="middle" light="true"/>
{priceAvgChangeASC && priceAvgChangeASC.length ? priceAvgChangeASC : <Typography variant="caption">No stock passes parameter</Typography>}
</List>
</Grid>
<Grid item xs={12} s={12} md={6} lg={6}>
<List dense='true'>
<Typography variant="h5">Price {<Chip label="Bearish" size="small"/>}</Typography>
<Typography variant="caption">Average Closing Price</Typography>
<Divider component="li" variant="middle" light="true"/>
{priceAvgChangeDESC && priceAvgChangeDESC.length ? priceAvgChangeDESC : <Typography variant="caption">No stock passes parameter</Typography>}
</List>
</Grid>
<Grid item xs={12} s={12} md={6} lg={6}>
<List dense='true'>
<Typography variant="h5">Volume {<Chip label="Gaining" size="small"/>}</Typography>
<Typography variant="caption">Average Daily Volume</Typography>
<Divider component="li" variant="middle" light="true"/>
{volumeAvgChangeASC && volumeAvgChangeASC.length ? volumeAvgChangeASC : <Typography variant="caption">No stock passes parameter</Typography>}
</List>
</Grid>
<Grid item xs={12} s={12} md={6} lg={6}>
<List dense='true'>
<Typography variant="h5">Volume {<Chip label="Losing" size="small"/>}</Typography>
<Typography variant="caption">Average Daily Volume</Typography>
<Divider component="li" variant="middle" light="true"/>
{volumeAvgChangeDESC && volumeAvgChangeDESC.length ? volumeAvgChangeDESC : <Typography variant="caption">No stock passes parameter</Typography>}
</List>
</Grid>
</Grid>
</Container>
</>
)
}
EDIT: The answered solution seems to solve the problem expect for one case in my code: As you can see, I have added the key inside the grid component as thats the first child. However Im still getting an error.
export const MarketSentiment = (props) =>
{
const { financialData } = props;
const ratingScaleASC = financialData && financialData[4] && sortAll(financialData[4], 'asc', 'rating_scale')
.filter((i) => i.rating_scale < 2.5 )
.slice(0, 4)
.map((stock, index) => (
<Grid key={index}>
<ListItem>
<ListItemAvatar>
<Avatar style={{ backgroundColor: GreenRank[index]}}>
{index + 1}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={stock.symbol}
secondary={financialData && financialData[5].filter((i) => i.symbol === stock.symbol).map(
filteredSymbol => (
<>
{filteredSymbol.company}
</>
))}/>
</ListItem>
</Grid>
));
const ratingScaleDESC = financialData.length && financialData[4] && sortAll(financialData[4], 'desc', 'rating_scale')
.filter((i) => i.rating_scale > 3.5)
.slice(0, 4)
.map((stock, index) => (
<Grid key={index}>
<ListItem>
<ListItemAvatar>
<Avatar style={{ backgroundColor: RedRank[index]}}>
{index + 1}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={stock.symbol}
secondary={financialData && financialData[5].filter((i) => i.symbol === stock.symbol).map(
filteredSymbol => (
<>
{filteredSymbol.company}
</>
))}/>
</ListItem>
</Grid>
));
const putCallDESC = financialData.length && financialData[3] && sortAll(financialData[3], 'desc', 'put_call_ratio')
.filter((i) => i.put_call_ratio > 1.5)
.slice(0, 4)
.map((stock, index) => (
<Grid key={index}>
<ListItem>
<ListItemAvatar>
<Avatar style={{ backgroundColor: RedRank[index]}}>
{index + 1}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={stock.symbol}
secondary={financialData && financialData[5].filter((i) => i.symbol === stock.symbol).map(
filteredSymbol => (
<>
{filteredSymbol.company}
</>
))}/>
</ListItem>
</Grid>
));
const putCallASC = financialData.length && financialData[3] && sortAll(financialData[3], 'asc', 'put_call_ratio')
.filter((i) => i.put_call_ratio < .5)
.slice(0, 4)
.map((stock, index) => (
<Grid key={index}>
<ListItem>
<ListItemAvatar>
<Avatar style={{ backgroundColor: GreenRank[index]}}>
{index + 1}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={stock.symbol}
secondary={financialData && financialData[5].filter((i) => i.symbol === stock.symbol).map(
filteredSymbol => (
<>
{filteredSymbol.company}
</>
))}/>
</ListItem>
</Grid>
));
return (
<>
<Container style={{ padding: "10px", paddingBottom:"40px" }} maxWidth="md">
<Typography variant="h2">Market Sentiment</Typography>
<Typography variant="overline">an overview of sentiments based off stocks in your bucket. Investor sentiment are based off the number of put to calls. </Typography>
<Grid justify="center" direction='row' container spacing={5} alignItems="stretch">
<Grid item xs={12} s={12} md={6} lg={6}>
<List dense='true'>
<Typography variant="h5">Rating {<Chip label="Bullish" size="small"/>}</Typography>
<Typography variant="caption">Investor Sentiment</Typography>
<Divider component="li" variant="middle" light="true"/>
{putCallASC && putCallASC.length ? putCallASC : <Typography variant="caption">No stock passes parameter</Typography>}
</List>
</Grid>
<Grid item xs={12} s={12} md={6} lg={6}>
<List dense='true'>
<Typography variant="h5">Rating {<Chip label="Bearish" size="small"/>}</Typography>
<Typography variant="caption">Investor Sentiment</Typography>
<Divider component="li" variant="middle" light="true"/>
{putCallDESC && putCallDESC.length ? putCallDESC : <Typography variant="caption">No stock passes parameter</Typography>}
</List>
</Grid>
<Grid item xs={12} s={12} md={6} lg={6}>
<List dense='true'>
<Typography variant="h5">Rating {<Chip label="Buy" size="small"/>}</Typography>
<Typography variant="caption">Analyst Sentiment</Typography>
<Divider component="li" variant="middle" light="true"/>
{ratingScaleASC && ratingScaleASC.length ? ratingScaleASC : <Typography variant="caption">No stock passes parameter</Typography>}
</List>
</Grid>
<Grid item xs={12} s={12} md={6} lg={6}>
<List dense='true'>
<Typography variant="h5">Rating {<Chip label="Sell" size="small"/>}</Typography>
<Typography variant="caption">Analyst Sentiment</Typography>
<Divider component="li" variant="middle" light="true"/>
{ratingScaleDESC && ratingScaleDESC.length ? ratingScaleDESC : <Typography variant="caption">No stock passes parameter</Typography>}
</List>
</Grid>
</Grid>
</Container>
</>
)
}
Error:
Each child in a list should have a unique "key" prop.
Check the render method of `ForwardRef(ListItemText)`. It was passed a child from MarketSentiment.
Upvotes: 0
Views: 990
Reputation: 333
the direct child after the map() should have the unique key.
in your code:
....
...
.map((stock, index) => (
<Grid>
<ListItem key={index}> // not the direct child
the direct child is the Grid
component
how it should be:
....
...
.map((stock, index) => (
<Grid key={index} > // put the unique key here
<ListItem >
EDIT:
you should implement the same fix to the other direct children every time you map over an array and return children
.
If the direct child is React Fragmants, don't forget to change it from the Shorthand
syntax to the normal
syntax.
That means: to change: <> </>
to: <React.Fragment></React.Fragment>
, to be able to add the key
property to it, since you can't add the property to the Shorthand version
in your code, please don't forget to fix the following ...
in your code:
....
...
.map(
filteredSymbol => (
<> // the direct child is React Fragmants, change it from the Shorthand to the normal React.Fragment. to be able to add the key property to it, since you can't add the property to the Shorthand version
{filteredSymbol.company}
</>
))}/>
how it should be:
....
...
.map((filteredSymbol, index)=> (
<React.Fragment key={index}> // put the unique key here
{filteredSymbol.company}
</React.Fragment>
))}/>
Upvotes: 4