user1032752
user1032752

Reputation: 871

Typescript generic and React components

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

Answers (1)

JDB
JDB

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

Related Questions