I have a List component and I would like to pass listItems from a parent. I just need to enforce that the prop I am passing is of Divider or ListItem component. How can I enforce that? I do not want to do at the run time as in this example but rather at compile time. Please help. Also how do I add keys while rendering the list.
This is my codesandbox.
index.tsx
import * as React from "react";
import ReactDOM from "react-dom/client";
import { StyledEngineProvider } from "@mui/material/styles";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import Divider from "@mui/material/Divider";
import ListItemText from "@mui/material/ListItemText";
import Demo from "./demo";
ReactDOM.createRoot(document.querySelector("#root")).render(
<React.StrictMode>
<StyledEngineProvider injectFirst>
<Demo
additionalMenuItems={[
<ListItem disablePadding>
<ListItemButton>
<ListItemText primary="Car" />
</ListItemButton>
</ListItem>,
<Divider />,
<ListItem disablePadding>
<ListItemButton>
<ListItemText primary="Bike" />
</ListItemButton>
</ListItem>
]}
/>
</StyledEngineProvider>
</React.StrictMode>
);
demo.tsx
import * as React from "react";
import Box from "@mui/material/Box";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Divider from "@mui/material/Divider";
import InboxIcon from "@mui/icons-material/Inbox";
import DraftsIcon from "@mui/icons-material/Drafts";
interface Props {
additionalMenuItems: (typeof Divider | typeof ListItem)[];
}
const BasicList = (props: Props) => {
return (
<Box sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
<nav aria-label="main mailbox folders">
<List>
<ListItem disablePadding>
<ListItemButton>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItemButton>
</ListItem>
</List>
</nav>
<Divider />
<nav aria-label="secondary mailbox folders">
<List>
<ListItem disablePadding>
<ListItemButton>
<ListItemText primary="Trash" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton component="a" href="#simple-list">
<ListItemText primary="Spam" />
</ListItemButton>
</ListItem>
{props.additionalMenuItems}
</List>
</nav>
</Box>
);
};
export default BasicList;
CodePudding user response:
Sadly, it isnt possible because of https://github.com/microsoft/TypeScript/issues/21699. We have to wait for TS support.
You could instead have the user pass a typed object which describes the structure, then resolve this in your core component.
index.tsx
import * as React from "react";
import ReactDOM from "react-dom/client";
import { StyledEngineProvider } from "@mui/material/styles";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import Divider from "@mui/material/Divider";
import ListItemText from "@mui/material/ListItemText";
import Demo from "./demo";
ReactDOM.createRoot(document.querySelector("#root")).render(
<React.StrictMode>
<StyledEngineProvider injectFirst>
<Demo
additionalMenuItems={[
{
type: "listitem",
children: (
<ListItemButton>
<ListItemText primary="Car" />
</ListItemButton>
)
},
{ type: "divider" },
{
type: "listitem",
children: (
<ListItemButton>
<ListItemText primary="Bike" />
</ListItemButton>
)
}
]}
/>
</StyledEngineProvider>
</React.StrictMode>
);
demo.tsx
import * as React from "react";
import Box from "@mui/material/Box";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Divider from "@mui/material/Divider";
import InboxIcon from "@mui/icons-material/Inbox";
import DraftsIcon from "@mui/icons-material/Drafts";
interface Props {
additionalMenuItems: Array<{type: 'divider'} | {type: 'listitem', children: JSX.Element}>;
}
const BasicList = (props: Props) => {
return (
<Box sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
<nav aria-label="main mailbox folders">
<List>
<ListItem disablePadding>
<ListItemButton>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItemButton>
</ListItem>
</List>
</nav>
<Divider />
<nav aria-label="secondary mailbox folders">
<List>
<ListItem disablePadding>
<ListItemButton>
<ListItemText primary="Trash" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton component="a" href="#simple-list">
<ListItemText primary="Spam" />
</ListItemButton>
</ListItem>
{props.additionalMenuItems.map((item) =>
item.type === 'listitem' ?
<ListItem disablePadding>{item.children}</ListItem>
:
<Divider />
)}
</List>
</nav>
</Box>
);
};
export default BasicList;
Up to you if the damaged API is worth the strong types.