I have a React component that accepts a SlotComponent
prop as well as a slotComponentProps
prop for props that are to be passed to the slot component:
interface MyComponentProps<T extends React.ElementType> {
SlotComponent: T
slotComponentProps: React.ComponentProps<T>
}
function MyComponent<T extends React.ElementType>({
SlotComponent,
slotComponentProps,
}: MyComponentProps<T>): React.ReactNode {
return (
<div>
<SlotComponent {...slotComponentProps} />
// Other things go here.
</div>
)
}
This partially works, but isn't as flexible as I would like it to be. The issues I have are:
SlotComponent
optional and default it to something like 'button'
, I get the TypeScript error:TS2322: Type '"button"' is not assignable to type 'T'.
'"button"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'ElementType<any, keyof IntrinsicElements>'.
SlotComponent = 'button',
~~~~~~~~~~~~~
slotComponentProps
optional, then I get this TypeScript error:TS2322: Type '{}' is not assignable to type 'LibraryManagedAttributes<T, any>'.
<SlotComponent {...slotComponentProps} />
~~~~~~~~~~~~~
slotComponentProps
optional and add {}
as a default, I get this TypeScript error:TS2322: Type '{}' is not assignable to type 'ComponentProps<T>'.
slotComponentProps = {},
~~~~~~~~~~~~~~~~~~
How can I update my types to allow me to achieve these changes?
Not sure which level of flexibility you would like to achieve, but I figured out the following solution, which allows the following:
SlotComponent
can be skipped. DEFAULT_ELEMENT_TYPE
will be used instead. slotComponentProps
still can be provided, type is controlled.slotComponentProps
can be skipped.slotComponentProps
is checked to be compatible with props of the SlotComponent
The solution is to make both parameters optional and default them in the function body in case of absence. Default generic type is used to control type of slotComponentProps
when SlotComponent
is not specified.
interface MyComponentProps<T extends React.ElementType> {
SlotComponent?: T
slotComponentProps?: React.ComponentProps<T>
}
const DEFAULT_ELEMENT_TYPE = 'button' as const;
type DefaultElementTypeType = typeof DEFAULT_ELEMENT_TYPE;
function MyComponent<T extends React.ElementType = DefaultElementTypeType>({SlotComponent, slotComponentProps}: MyComponentProps<T>): React.ReactNode {
const Component: React.ElementType = SlotComponent ?? DEFAULT_ELEMENT_TYPE;
return (
<div>
<Component {...slotComponentProps} />
// Other things go here.
</div>
)
}