Christopher Francisco
Christopher Francisco

Reputation: 16278

How to type props for Material UI ListItem?

I'm trying to write a wrapper component that will decide whether to have the ListItem render a react-router-dom Link or a button

// inside some component
let props

if ("uri" in navItem) {
  props = { component: Link, to: navItem.uri }           
} else {
  props = { button: true, onClick: navItem.onClick }   
}



return (
  <ListItem {...props}>
    ...some more code ...
  </ListItem
)

It's complaining on ListItem saying that no overload matches this call. I'm guessing the error comes because the props variable isn't typed. However, I'm unable to understand how could I type this, it's too complex for my TS level.

Could I get some directions on how to type these?

Edit:

To add more information, I'm just trying to DRY this up:

export type NavItem = Omit<BaseNavItem, 'onClick'> | Omit<BaseNavItem, 'uri'>;

type BaseNavItem = {
  icon: React.ReactNode;
  label: string;
  uri: string;
  onClick: () => void;
};

// MyComponent.tsx
//   it will render Link or buttons depending
//   on whether uri or onClick was passed in the navItem
if ("uri" in navItem) {
  return (
    <li>
      <ListItem key={label} component={RouterLink} to={navItem.uri}>
        <ListItemIcon>{icon}</ListItemIcon>
        <ListItemText primary={label} />
      </ListItem>
    </li>
  )
} else {
  return (
    <ListItem key={label} button onClick={navItem.onClick}>
      <ListItemIcon>{icon}</ListItemIcon>
      <ListItemText primary={label} />
    </ListItem>
  )
}

Upvotes: 2

Views: 2166

Answers (1)

Ahmed Mokhtar
Ahmed Mokhtar

Reputation: 2506

This is a simple solution:

import React from "react";
import { ListItem, ListItemIcon, ListItemText } from "@material-ui/core";
import { Link } from "react-router-dom";

interface INavItem {
  icon: React.ReactNode;
  label: string;
  // ? makes the prop optional
  // it can be undefined
  uri?: string;
  onClick?: () => void;
}

interface Props {
  navItem: INavItem;
}

function NavItem({ navItem }: Props) {
  const Inner = (
    <>
      <ListItemIcon>{navItem.icon}</ListItemIcon>
      <ListItemText primary={navItem.label} />
    </>
  );
  if (navItem.uri) {
    return (
      <ListItem component={Link} to={navItem.uri}>
        {Inner}
      </ListItem>
    );
  }

  return (
    <ListItem button onClick={navItem.onClick}>
      {Inner}
    </ListItem>
  );
}

export default NavItem;

As for defining props depending on the component prop it's far more complicated and it's unnecessary in this situation.

Check the docs: https://material-ui.com/guides/typescript/#usage-of-component-prop

Example:

import React, { ElementType } from "react";
import {
  ListItem,
  ListItemIcon,
  ListItemText,
  ListItemProps,
} from "@material-ui/core";
import { Link } from "react-router-dom";
import { People, Edit } from "@material-ui/icons";

interface INavItem {
  icon: React.ReactNode;
  label: string;
}

export type MyListItemProps<C extends ElementType> = ListItemProps<
  C,
  { component?: C }
> &
  INavItem;

function NavItem<C extends ElementType>({
  icon,
  label,
  ...props
}: MyListItemProps<C>) {
  return (
    <ListItem {...props}>
      <ListItemIcon>{icon}</ListItemIcon>
      <ListItemText primary={label} />
    </ListItem>
  );
}

const navItems = [
  { to: "/users", icon: People, label: "Users" },
  { icon: Edit, label: "Edit", onClick: () => {} },
];

// Usage
const Nav = () => {
  return navItems.map(({ to, icon, label, onClick }) => {
    if (to) {
      return <NavItem<Link> to={to} icon={icon} label={label} />;
    }
    return <NavItem icon={icon} label={label} button onClick={onClick} />;
  });
};

export default Nav;

or

const navItems = [
  { to: "/users", icon: People, label: "Users" },
  { icon: Edit, label: "Edit", onClick: () => {}, button: true },
];

// Usage
const Nav = () => {
  return navItems.map(({ to, ...props }) => {
    if (to) {
      return <NavItem<Link> to={to} {...props} />;
    }
    return <NavItem {...props} />;
  });
};

export default Nav;

Upvotes: 2

Related Questions