I'm trying to make a reusable InfoBlock component. This component renders a list of items. Each item has a label, icon and value. The problem that I don't know how to map available INFO_BLOCK_ITEMS
to data object and render only items which present in data object.
Full list of all available labels and icons looks like this:
const INFO_BLOCK_ITEMS = {
author: {
label: "Author",
icon: AccountTreeIcon
},
date: {
label: "Created",
icon: FaceRetouchingNaturalIcon
},
rawMedias: {
label: "RAW Medias",
icon: InsertDriveFileOutlinedIcon
},
face: {
label: "Faces",
icon: ScheduleOutlinedIcon,
faceAction: true,
},
s3Source: {
label: "S3 Source",
icon: AcUnitIcon
}
};
Data object which I pass to InfoBlock
component along with dataType (for another page, the data structure will be different but it will contain the keys from INFO_BLOCK_ITEMS
:
const DATASET = {
author: "[email protected]",
date: 1669208819,
rawMedias: "Video name 1, Video name 2, Video name 3",
face: ""
};
<InfoBlock data={DATASET} type={"dataset"} />
The result should be a list like this for every key in data object:
<Stack>
<AccountTreeIcon />
<Stack>
<Typography>Author</Typography>
<Typography>[email protected]</Typography>
</Stack>
</Stack>
Here's a Codesandbox with hardcoded list: https://codesandbox.io/s/info-block-forked-0bwrze?file=/src/InfoBlock.tsx
UPD
Most of the list items values are strings. If value is an empty string then "Empty" should be rendered otherwise provided string. But some items values if empty should render a button. How to handle this case? Also updated Codesandbox
{
Object.keys(data).map((key, _index) => {
const infoBlockItem = INFO_BLOCK_ITEMS[key];
return (
<Stack key={_index} direction={"row"} gap={"10px"}>
{infoBlockItem.icon}
<Stack direction={"row"} gap={"20px"}>
<Typography>{infoBlockItem.label}</Typography>
{infoBlockItem[key].action ? ( // this gaves error might be because of TS but I don't know how to fix that
<Button onClick={faceAction}>
Do something
</Button>
) : (
<Typography>
{headerData[key]
? headerData[key]
: 'Empty'}
</Typography>
)}
</Stack>
</Stack>
);
})
}
How to pass onClick prop then?
<InfoBlock data={DATASET} faceAction={() => console.log('Face button clicked')} />
CodePudding user response:
You don't need to pass type to InfoBlock component.
data in Rawmedia type should be number.
Use icons as React component.
Hope it helps.
Types:
export type Dataset = {
author: string;
date: number;
rawMedias: string;
face: string;
};
export type RawMedia = {
s3Source: string;
author: string;
date: number;
face: string;
};
InfoBlock Component:
const INFO_BLOCK_ITEMS = {
author: {
label: "Author",
icon: <AccountTreeIcon />
},
date: {
label: "Created",
icon: <FaceRetouchingNaturalIcon />
},
rawMedias: {
label: "RAW Medias",
icon: <InsertDriveFileOutlinedIcon />
},
face: {
label: "Faces",
icon: <ScheduleOutlinedIcon />,
action: () => console.log("If no data then button renders")
},
s3Source: {
label: "S3 Source",
icon: <AcUnitIcon />
}
};
interface IInfoBlockProps {
data: Dataset | RawMedia;
}
function InfoBlock({ data }: IInfoBlockProps) {
return(
<Stack gap={"20px"}>
{
Object.keys(data).map((key, _index) => {
const infoBlockItem = INFO_BLOCK_ITEMS[key];
return (
<Stack key={_index} direction={"row"} gap={"10px"}>
{infoBlockItem.icon}
<Stack direction={"row"} gap={"20px"}>
<Typography>{infoBlockItem.label}</Typography>
<Typography>{data[key]}</Typography>
</Stack>
</Stack>
);
})
}
</Stack>
)
}
App Component:
const DATASET = {
author: "[email protected]",
date: 1669208819.837662,
rawMedias: "Video name 1, Video name 2, Video name 3",
face: ""
};
const RAW_MEDIA = {
s3Source: "https://example.com",
author: "[email protected]",
date: 1669208819.837662,
face: "Some face"
};
function App() {
return (
<div>
<InfoBlock data={DATASET} />
<InfoBlock data={RAW_MEDIA} />
</div>
);
}