I'm implementing a component that makes it easier for users to add several items to a page. Since I have several types of items that may be added by a user, I figured having a single component with a "children" parameter would be most useful.
However, the implementation is having trouble with the "key" parameter when mapping over the children elements.
function ExpandableSet({
startingSet,
itemTitle,
children
})
{
const largestId = useMemo(() => {
return startingSet.reduce((largestId, item) =>
if (item.id > largestId){
return item.id
}
return largestId
}, 0)
}, [])
const [itemCount, setItemCount] = useState(largestId + 1);
const [items, setItems] = useState(startingSet);
return(
<div className="expandable-set">
{ items.map( item =>
<div className="expanded-item" id={item['key']} key={item['key']}> { children } </div>
)}
</div>
<button onClick={() =>
{
items.push({"key" : itemCount});
setItemCount(itemCount + 1);
}
}>
Add {itemTitle}
</button>
)
}
When ExpandableSet
is used, it's used like this:
...
<div className="container-for-expandable-set">
<ExpandableSet
startingSet={[]}
itemTitle="Some item I want a lot of" />
<SomeItem opts=opts />
</ExpandableSet>
</div>
...
Since I have no idea how many SomeItem
elements there will be, there's no way to pass keys at the parent level without managing state there as well. This would defeat the purpose of the ExpandableSet
component (maybe this is the right answer?)
When this is output, I see the standard each child in a list should have a unique 'key' prop
error.
HTML Output
...
<div class="expandable-set">
<div class="expanded-item" id="0">
<div class="child-template-passed">
...
</div>
</div>
<div class="expanded-item" id="1">
<div class="child-template-passed">
...
</div>
</div>
</div>
...
Why am I still seeing the key
error?
You begin with a starting set which may have N elements, each with presumably any ID from zero upwards, and then a counter that starts at zero.
So if I add an item to the set via the button on the page, it will have ID zero. But what if an item in the starting set already has an ID of zero? You now have a duplicate ID in the set.
You need to find the largest ID in the starting set, and use this ID plus one as the initial value for itemCount
.
const largestID = useMemo(() => {
return startingSet.reduce((largestID, item) => {
if (item.key > largestID) {
return item.key
}
return largestID
}, 0)
}, [startingSet])
const [items, setItems] = useState(startingSet);
const [itemCount, setItemCount] = useState(largestID + 1);