Reputation: 16278
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
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