I'm attempting to write a universal wrapper component that accepts an as prop which indicates the element to render and also requires any props that the element being rendered requires. With help from this answer, I have a working example:
import React, { ComponentType, ElementType, ComponentProps } from "react"
type WrapperProps<P = {}> = {
as: ComponentType<P> | ElementType
} & P
const Wrapper = <P = {}>({ as: Element, ...rest }: WrapperProps<P>) => <Element {...rest} />
const Link = ({ href }: { href: string }) => <a href={href}>Click Me</a>
const Test = () => <Wrapper as={Link} /> // Should error as `href` is missing.
const Test2 = () => <Wrapper as={Link} to='/' /> // Should error as `to` is not a valid prop.
const Test3 = () => <Wrapper as={Link} href='/' /> // Should compile.
I'm unable to wrap my head around why the Wrapper component requires it's own generic type P. In other words, why this wouldn't work:
const Wrapper = ({ as: Element, ...rest }: WrapperProps) => <Element {...rest} />
The type WrapperProps already defines P as a generic which defaults to an object. Why must I redefine that when typing Wrapper? Is React/TS somehow passing a value for this variable automatically when I call Wrapper and if not, what extra information does that compiler get from me duplicating the declaration that P defaults to an empty object?
Update
I wanted to provide some additional context. I understand that if I were to define this type inline, I would need to add the generic to Wrapper as obviously it needs to come from somewhere:
const Wrapper = <P = {}>({ as: Element, ...rest }: {
as: ComponentType<P> | ElementType
} & P) => <Element {...rest} />
I also understand that I were passing an explicit type for the generic when calling Wrapper then Wrapper would need it's own generic to be able to "pass" that to WrapperProps:
<Wrapper<ComponentProps<typeof Link>> as={Link} href='/' />
What I'm missing is that since I am not passing any type to Wrapper why do I need it to accept a generic? In other words, the type WrapperProps already knows that the type P defaults to an empty object, why does the function Wrapper need to redeclare this?
It seems redundant for Wrapper to set P to an empty object and then pass that type to WrapperProps as WrapperProps could have just set it's own P to an empty object.
Wrapper, but then it would be a required generic, which might be frustrating to use.