Home > Blockchain >  Enforce prop to only accept certain components like ListItem and Divider and add keys - React Typesc
Enforce prop to only accept certain components like ListItem and Divider and add keys - React Typesc

Time:09-08

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.

  • Related