Reputation: 871
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.
Upvotes: 0
Views: 255
Reputation: 25820
Wrapper
is a function that must take an argument. The argument is of type WrapperProps
, which is generic.
If you didn't make Wrapper
generic, then WrapperProps
would either need to declare its generic type explicitly or else it would fall back to its default:
// WrapperProps is implicitly WrapperProps<{}>
const Wrapper = ({ as: Element, ...rest }: WrapperProps) => <Element {...rest} />
const Wrapper = ({ as: Element, ...rest }: WrapperProps<Link>) => <Element {...rest} />
In this sense, it's no different than a chain of two function, where you want to pass a value to the first so that it can pass it to the second:
function foo(p = {}) { return p; }
function bar(p = {}) { return foo(p); }
bar({ baz: "value" }); // returns { baz: "value" }
Upvotes: 1