I have a React button container component that returns a regular button, an icon button, or an icon-only button based on the type provided by the prop used by the user. The following are the related type definitions of the ButtonContainer component and Props:
type ButtonWrapperProp = {
label?:string;
icon?:React.ReactNode;
onClick:()=>void;
type:'text'|'icon'|'iconOnly';
}
export const ButtonContainer = (props: ButtonWrapperProps) => {
const {
type = 'text',
onClick,
icon = <></>,
label = '',
} = props;
const rendorButton = () => {
switch (type) {
case 'text':
return (
<Button onClick={onClick}>{label} <Button/>
);
case 'iconOnly':
return (
<IconButton onClick={onClick}>
{icon}
</IconButton>
);
case 'icon':
return (
<Button startIcon={icon}>
{label}
</Button>
);
default:
return (
<Button onClick={onClick}>{label} <Button/>
);
}
};
return <>{rendorButton()}</>;
};
Here's how I use the ButtonContainer component:
<ButtonContainer type="iconOnly" onClick={onClick} icon={<DeleteIcon />}/>
<ButtonContainer type="text" onClick={onClick} label='Text button'/>
<ButtonContainer type="icon" onClick={onClick} label='Text and icon button' icon={<DeleteIcon />}/>
In the above code, the icon prop is optional because it is only used for icon buttons with icons and regular buttons. I want the icon prop to be optional only if the type passed from ButtonContainer is text and it should be a required prop if the type passed to ButtonContainer is icon or iconOnly.
I would choose to use Distinguish the union type.
The type attribute acts as a discriminator, allowing you to narrow down ButtonWrapperProps to union members.
Also note that I can make each union member's properties required, which gives a good experience when using them - TS is smart enough to prompt which additional properties are required after the type is set .
type TextButtonProps = { type:'text' label:string; onClick:()=>void; } type IconOnlyButtonProps = { type:'iconOnly' icon:React.ReactNode; onClick:()=>void; } type IconButtonProps = { type:'icon' icon:React.ReactNode; label:string; onClick:()=>void; } type ButtonWrapperProps = TextButtonProps | IconOnlyButtonProps | IconButtonProps; export const ButtonContainer = (props: ButtonWrapperProps) => { const renderButton = () => { switch (props.type) { case 'text': return ( <Button onClick={props.onClick}>{props.label} </Button> ); case 'iconOnly': return ( <IconButton onClick={props.onClick}> {props.icon} </IconButton> ); case 'icon': return ( <Button startIcon={props.icon}> {props.label} </Button> ); } }; return <>{renderButton()}</>; };