CodeSandbox Example for this issue.
I have defined an icon library that exports several icons. Each Icon forwards a ref to its internal svg. Here’s an example:
type ReactIconProps = SVGProps<SVGSVGElement>;
function CheckIconInner(props: ReactIconProps, ref: Ref<SVGSVGElement>) {
return (
<svg
fill="none"
height="1em"
ref={ref}
stroke="currentColor"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M20 6L9 17L4 12"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
/>
</svg>
);
}
const CheckIcon = forwardRef(CheckIconInner);
I want to use these icons as a prop in a button component so that the button can include an icon. See below:
type ReactButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
export interface ButtonProps extends ReactButtonProps {
Icon?: ComponentType<ReactIconProps>;
}
function Button({ children, Icon, ...props }: ButtonProps) {
return (
<button {...props}>
{Icon !== undefined && <Icon />}
{children}
</button>
);
}
export default function App() {
return <Button Icon={CheckIcon}>OK</Button>;
}
However, this approach throws a typescript error when an icon is passed to the button (<Button Icon={CheckIcon}>OK</Button>
):
TS2322: Type 'ForwardRefExoticComponent<Omit<ReactIconProps, "ref"> & RefAttributes<SVGSVGElement>>' is not assignable to type 'ComponentType<ReactIconProps> | undefined'.
Type 'ForwardRefExoticComponent<Omit<ReactIconProps, "ref"> & RefAttributes<SVGSVGElement>>' is not assignable to type 'FunctionComponent<ReactIconProps>'.
Types of parameters 'props' and 'props' are incompatible.
Type 'ReactIconProps' is not assignable to type 'Omit<ReactIconProps, "ref"> & RefAttributes<SVGSVGElement>'.
Type 'SVGProps<SVGSVGElement>' is not assignable to type 'RefAttributes<SVGSVGElement>'.
Types of property 'ref' are incompatible.
Type 'LegacyRef<SVGSVGElement> | undefined' is not assignable to type 'Ref<SVGSVGElement> | undefined'.
Type 'string' is not assignable to type 'Ref<SVGSVGElement> | undefined'.
The issue is that the icon requires a ref, but we are not passing it one.
Two questions:
- How can we specify that the ref passed to icon component is optional?
- Is the use of ref in the Icon component a good practice? I have seen lots of advice that refs should not be overused, but see that may component libraries use it extensively.
Edit
I found the answer to #2 in an issue in the heroicons repository – plenty of use cases!
2
Answers
I finally figured out the answer. Instead of typing the
Icon
prop asComponentType<ReactIconProps>
:I changed it to:
The new type definition
ReactIconComponent
essentially replaces theref
property inReactIconProps
withRefAttributes<SVGSVGElement>
. This types the ref correctly for an SVG element.Here's the fixed CodeSandbox example.
You can make any field optional in your interface declaration by partial extensions of the original type or whatever part you need. (This can also be done via omit, &, as, etc)
For example:
ExampleProps now contains all fields in ReactIconProps, but as optional items. With customItem being the only required field.