I can't get rid of the classic React "Each child in the list should have a unique key".
I'm rendering a big array, with items that can have sub items.
To prevent all of this, i created a data structure with unique id for every item.
This is my data:
export const assetListConfig: RolesListItem[] = [
{
id: 1,
title: t('Asset creation'),
name: 'Asset-Create',
type: 'switch',
},
{
id: 2,
title: t('Edit asset info'),
name: 'Asset-Edit',
type: 'switch',
},
{
id: 3,
title: t('Archive'),
name: 'Asset-Archive',
type: 'switch',
},
{
id: 4,
title: t('Delete'),
name: 'Asset-Delete',
type: 'switch',
subLists: [
{
subListWatch: 'Asset-Delete',
items: [
{
id: 5,
title: t('Bulk deletion'),
name: 'Asset-Delete-Bulk',
type: 'switch',
},
],
},
],
},
{
id: 6,
title: t('Resources'),
name: 'Asset-Manage-Resources',
type: 'switch',
},
{
id: 7,
title: t('Enable public'),
name: 'Asset-Set-Public',
type: 'switch',
},
{
id: 8,
title: t('Enable redirect'),
name: 'Asset-Set-Redirect',
type: 'switch',
},
{
id: 9,
title: t('Enable tickets'),
name: 'Asset-Enable-Tickets',
type: 'switch',
},
{
id: 10,
title: t('Edit hierarchy'),
name: 'Asset-Edit-Hierarchy',
type: 'switch',
itemWatch: ['Asset-Edit'],
},
{
id: 11,
title: t('Locations'),
name: 'Asset-Location',
type: 'switch',
},
{
id: 12,
title: t('Manage asset tags'),
name: 'Asset-Manage-Tags',
type: 'switch',
},
{
id: 13,
title: t('Logbook'),
name: 'Asset-Logbook-Read',
type: 'switch',
subLists: [
{
subListWatch: 'Asset-Logbook-Read',
items: [
{
id: 14,
title: t('Comment'),
name: 'Asset-Logbook-Comment',
type: 'switch',
},
{
id: 15,
title: t('Allow export'),
name: 'Asset-Logbook-Export',
type: 'switch',
},
],
},
],
},
{
id: 16,
title: t('Components'),
name: 'Component-Read',
type: 'switch',
subLists: [
{
subListWatch: 'Component-Read',
items: [
{
id: 17,
title: t('Instances'),
name: 'Component-Instance',
type: 'radio',
radioOptions: [
{ label: t('Manage'), value: 'Component-Instance-Manage' },
{
label: t('Delete'),
value: 'Component-Instance-Delete',
inputWatch: 'Component-Instance-Manage',
},
],
},
{
id: 18,
title: t('Models'),
name: 'Component-Model',
type: 'radio',
radioOptions: [
{
label: t('Manage'),
value: 'Component-Model-Manage',
inputWatch: 'Component-Instance-Manage',
},
{
label: t('Delete'),
value: 'Component-Model-Delete',
inputWatch: 'Component-Model-Manage',
},
],
},
],
},
],
},
{
id: 19,
title: t('Workshifts'),
name: 'Asset-Running-Read',
type: 'switch',
subLists: [
{
subListWatch: 'Asset-Running-Read',
items: [
{
id: 20,
title: t('Manage status'),
name: 'Asset-Running-Status-Manage',
type: 'switch',
},
{
id: 21,
title: t('Manage expectations'),
name: 'Asset-Running-Expectation-Manage',
type: 'switch',
},
{
id: 22,
title: t('Manage schedules'),
name: 'Asset-Running-Schedule-Manage',
type: 'switch',
},
],
},
],
},
];
I render lists with .map also for radiogroup options and item sublists.
This is the component in which I get the error:
import shortid from 'shortid'
export const UserRolesList: React.FC<ListItemProps> = ({
listConfig,
userData,
isSubList = false,
}) => {
handleChange ecc ecc...
return (
<RolesListWrapper isSubList={isSubList}>
<ul
className="list--unbulleted display--ib full-width"
>
/* FIRST LIST RENDERING */
**{listConfig.map((listItem, index) => {
const {
name,
title,
type,
radioOptions = [],
selectOptions = [],
id: skillId,
subLists,
} = listItem;**
const visibleOptions = renderVisibleOptions(radioOptions);
return (
<>
{isListItemVisible(listItem) ? (
**<li
className={`pad pad-double--sides shadow--list-item ${
index !== listConfig.length - 1 ? 'push--bottom' : ''
}`}
key={skillId}
style={
isSubList
? {
padding: '0',
boxShadow: 'none',
}
: {
width: 'auto',
minWidth: '350px',
}
}
>**
<div className="full-width flex--space-items">
<div
className="pad flex--align-center"
style={{
paddingLeft: isSubList ? 'var(--base-space)' : '',
flexBasis: '160px',
}}
>
{title}
</div>
{type === 'switch' ? (
<div className="pad flex--align-center">
<Switch
onColor="#21b151"
offColor="#e02f2f"
checked={findActiveSkill(name)}
onChange={() => handleSkillChange(name)}
disabled={isAddingSkill || isRemovingSkill}
/>
</div>
) : null}
{type === 'radio' ? (
**<div
className="flex-item--fill"
style={{
display: 'grid',
gridTemplateColumns: 'repeat(3,1fr)',
}}
>
/* SECOND LIST RENDERING */
{visibleOptions.map((opt) => (
<div
className="flex--align-center"
style={{
gridColumn: opt.value.includes('Delete')
? '3'
: undefined,
}}
key={shortid.generate()}
>**
<input
type="checkbox"
className="qtrack-form-input__selection"
value={opt.value}
id={opt.value}
checked={findActiveSkill(opt.value)}
onChange={(e) =>
handleSkillChange(e.target.value)
}
disabled={isAddingSkill || isRemovingSkill}
/>
<label
style={{ margin: '5px 0' }}
htmlFor={opt.value}
>
{opt.label}
</label>
</div>
))}
</div>
) : null}
{type === 'select' ? (
<div style={{ margin: '0', minWidth: '220px' }}>
<SimpleSelect
selectOptions={selectOptions}
value={selectOptions.find((opt) =>
skills.includes(opt.value),
)}
isMulti={false}
isClearable
errors={undefined}
onChange={(e: string) => handleSelectChange(e, name)}
name="roles-select"
isDisabled={isAddingSkill || isRemovingSkill}
/>
</div>
) : null}
</div>
{subLists ? (
<SubListHandler subLists={subLists} userData={userData} />
) : null}
</li>
) : null}
</>
);
})}
</ul>
</RolesListWrapper>
);
};
And this is the component in which I render the sublists:
export const SubListHandler: React.FC<{
subLists: RolesList[];
userData: UserData;
}> = ({ subLists, userData }) => (
<>
/* THIRD AND LAST LIST RENDER */
{subLists.map((list) => {
const { subListWatch, items: subItems } = list;
const isSubListVisible = userData.skills.includes(subListWatch);
**const subListId = shortid.generate();**
return (
<div key={subListId}>
{isSubListVisible ? (
**<div>**
{list.title ? (
<div className="txt--bold pad--left push--top">
{list.title}
</div>
) : null}
<div className="push--ends pad--left">
<UserRolesList
listConfig={subItems}
userData={userData}
isSubList
/>
</div>
</div>
) : null}
</>
);
})}
</>
);
As you can see, I'm using unique IDs for all the three map methods in my component.
This is confirmed when I log the IDs I used in the console. They're all different.
I don't know what I'm doing wrong or if I'm messing something with the conditional rendering or nested components
I tried to highlight when the list rendering happens in the code.
This seems pretty straighforward to me as I have unique ids for every list child, but it always throws me an error.
Let me know if you find something wrong with my code.
Thanks a lot!
You need to use the unique id in the 'key' prop of the list items. ie. -
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
So I have a react component set up to map through all the items in my array to display them on the page. I'm importing my component onto my homepage and passing the object as a prop from the imported component. However, when I load the page, only one item from the object is being rendered. I'm not entirely sure if I'm passing my object correctly. Any help would be appreciated! Code is below.
This is my Modal component. I'm mapping through the listGroupArray that has a spread operator with my data that is being passed from the home page.
export default function ModalButton({ setData, title, arrayData, dataTitle }) {
const [show, setShow] = useState(false);
const [button, setButton] = useState("Choose...")
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const listGroupArray = [{...arrayData}]
const changeButton = e => setButton(e)
return (
<>
<h5 className="inputFont text-center">{title}</h5>
<Button style={{ backgroundColor: "black", opacity: "1", color: "white", borderColor: "red" }} variant="primary" className="w-100 mb-4 inputFont" onClick={handleShow}>
{button}
</Button>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton className="modal-bg inputFont">
{dataTitle}
</Modal.Header>
<Modal.Body className="modal-bg">
<ListGroup>
{listGroupArray.map(item => (
<ListGroup.Item key={item.id} className="modal-bg">
<Button
style={{
backgroundColor: "black",
opacity: ".8",
color: "white",
borderColor: "red",
}}
className="inputFont w-100"
name={item.name}
value={item.value}
onClick={(e) => {
setData(item.value);
changeButton(item.name);
handleClose();
}}
>
{item.name}
</Button>
</ListGroup.Item>
))}
</ListGroup>
</Modal.Body>
</Modal>
</>
);
}
This is my homepage where I'm passing the array data as an object. I'm pretty sure this is where I'm going wrong. When I load the page, the component should render all the data in the object, however it's only rendering the last data, Classics.
<Modal title="Genre"
dataTitle="Pick A Genre"
setData={setGenrelist}
arrayData={
{
id: 1,
name: "Action and Adventure",
value: "10673,10702,11804,11828,1192487,1365,1568,2125,2653,43040,43048,4344,46576,7442,75418,76501,77232,788212,801362,899,9584"
},
{
id: 2,
name: "Musicals",
value: "13335,13573,32392,52852,55774,59433,84488,88635"
},
{
id: 3,
name: "Sci-Fi",
value: "1492,108533,11014,1372,1568,1694,2595,2729,3327,3916,47147,4734,49110,50232,52780,52849,5903,6000,6926,852491"
},
{
id: 4,
name: "Fantasy",
value: "9744"
},
{ id: 5,
name: "Thrillers",
value: "10306,10499,10504,10719,11014,11140,1138506,1321,1774,3269,43048,46588,5505,58798,65558,6867,75390,78507,799,852488,8933,98911,9147,972"
},
{
id: 6,
name: "Anime",
value: "10695,11146,2653,2729,3063,413820,452,6721,9302,7424"
},
{
id: 7,
name: "Children and Family",
value: "10056,27480,27950,28034,28083,28233,48586,5455,561,6218,6796,6962,78120,89513,783"
},
{
id: 8,
name: "Comedies",
value: "1009,10256,10375,105,10778,11559,11755,1208951,1333288,1402,1747,17648,2030,2700,31694,3300,34157,3519,3996,4058,4195,43040,4426,4906,52104,52140,52847,5286,5475,5610,56174,58905,59169,61132,61330,6197,63092,63115,6548,711366,7120,72407,7539,77599,77907,78163,78655,79871,7992,852492,869,89585,9302,9434,9702,9736"
},
{
id: 9,
name: "Documentaries",
value: "10005,10105,10599,1159,15456,180,2595,2616,2760,28269,3652,3675,4006,4720,48768,49110,49547,50232,5161,5349,55087,56178,58710,60026,6839,7018,72384,77245,852494,90361,9875"
},
{
id: 10,
name: "Dramas",
value: "11,11075,11714,1208954,1255,12995,13158,2150,25955,26009,2696,2748,2757,2893,29809,3179,31901,34204,3653,3682,384,3916,3947,4282,4425,452,4961,500,5012,52148,52904,56169,58755,58796,59064,6206,62235,6616,6763,68699,6889,711367,71591,71591,72354,7243,7539,75459,76507,78628,852493,89804,9299,9847,9873,5763"
},
{
id: 11,
name: "Sports",
value: "180,25788,4370,5286,7243,9327"
},
{
id: 12,
name: "Horror",
value: "10695,10944,1694,42023,45028,48303,61546,75405,75804,75930,8195,83059,8711,89585"
},
{
id: 13,
name: "Romance",
value: "29281,36103,502675"
},
{
id: 14,
name: "Classics",
value: "10032,11093,13158,29809,2994,31273,31574,31694,32392,46553,46560,46576,46588,47147,47465,48303,48586,48744,76186"
}
}
/>
screenshot of the homepage
This image shows the component only rendering one data item which is Classics. Any advice on how to get all data rendered would be greatly appreciated! Thanks!
The error is in how you referenced the arrayData and the problematic curly brackets you used on an array. In your JSX code, you have a syntax error, you are supposed to enclose arrays in curly brackets, or better still just separate them to their own variable.
Your JSX should then look something like this:
function JSX(props) {
const arrayData = [
{
id: 1,
name: "Action and Adventure",
value:
"10673,10702,11804,11828,1192487,1365,1568,2125,2653,43040,43048,4344,46576,7442,75418,76501,77232,788212,801362,899,9584",
},
{
id: 2,
name: "Musicals",
value: "13335,13573,32392,52852,55774,59433,84488,88635",
},
{
id: 3,
name: "Sci-Fi",
value:
"1492,108533,11014,1372,1568,1694,2595,2729,3327,3916,47147,4734,49110,50232,52780,52849,5903,6000,6926,852491",
},
{
id: 4,
name: "Fantasy",
value: "9744",
},
{
id: 5,
name: "Thrillers",
value:
"10306,10499,10504,10719,11014,11140,1138506,1321,1774,3269,43048,46588,5505,58798,65558,6867,75390,78507,799,852488,8933,98911,9147,972",
},
{
id: 6,
name: "Anime",
value: "10695,11146,2653,2729,3063,413820,452,6721,9302,7424",
},
{
id: 7,
name: "Children and Family",
value:
"10056,27480,27950,28034,28083,28233,48586,5455,561,6218,6796,6962,78120,89513,783",
},
{
id: 8,
name: "Comedies",
value:
"1009,10256,10375,105,10778,11559,11755,1208951,1333288,1402,1747,17648,2030,2700,31694,3300,34157,3519,3996,4058,4195,43040,4426,4906,52104,52140,52847,5286,5475,5610,56174,58905,59169,61132,61330,6197,63092,63115,6548,711366,7120,72407,7539,77599,77907,78163,78655,79871,7992,852492,869,89585,9302,9434,9702,9736",
},
{
id: 9,
name: "Documentaries",
value:
"10005,10105,10599,1159,15456,180,2595,2616,2760,28269,3652,3675,4006,4720,48768,49110,49547,50232,5161,5349,55087,56178,58710,60026,6839,7018,72384,77245,852494,90361,9875",
},
{
id: 10,
name: "Dramas",
value:
"11,11075,11714,1208954,1255,12995,13158,2150,25955,26009,2696,2748,2757,2893,29809,3179,31901,34204,3653,3682,384,3916,3947,4282,4425,452,4961,500,5012,52148,52904,56169,58755,58796,59064,6206,62235,6616,6763,68699,6889,711367,71591,71591,72354,7243,7539,75459,76507,78628,852493,89804,9299,9847,9873,5763",
},
{
id: 11,
name: "Sports",
value: "180,25788,4370,5286,7243,9327",
},
{
id: 12,
name: "Horror",
value:
"10695,10944,1694,42023,45028,48303,61546,75405,75804,75930,8195,83059,8711,89585",
},
{
id: 13,
name: "Romance",
value: "29281,36103,502675",
},
{
id: 14,
name: "Classics",
value:
"10032,11093,13158,29809,2994,31273,31574,31694,32392,46553,46560,46576,46588,47147,47465,48303,48586,48744,76186",
},
];
return (
<Modal
title="Genre"
dataTitle="Pick A Genre"
setData={(data) => console.log(data)}
arrayData={arrayData}
/>
);
}
The next bug you had was in using the spread operator. While arrays are technically objects in JavaScript they can't be spread with curly braces. This [{...arrayData}] is syntactically incorrect. Instead it should be [...arrayData]. With these in place, your code should run correctly.
I made a sandbox of your code in a working state for reference, check it out here:
https://codesandbox.io/s/young-snowflake-efqwk?file=/src/ModalButton.js:877-883