0

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.

user1032752
  • 751
  • 1
  • 11
  • 28
  • You don't have to set a default generic for `Wrapper`, but then it would be a required generic, which might be frustrating to use. – JDB Aug 25 '22 at 19:59

1 Answers1

1

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" }
JDB
  • 25,172
  • 5
  • 72
  • 123
  • Thanks! Isn't the value I'm passing `P = {}` already the same as the implicit value of `P` in `WrapperProps`? Your function analogy makes sense but only when `bar` is called with a non-empty object. E.g. `bar()` is identical to `bar({})`. – user1032752 Aug 24 '22 at 14:55
  • @user1032752 - Wrapper is being assigned a generic anonymous function. For example, given `const foo = (a:T) => T`, then `foo` is a function that will return the same type as the argument it's passed. E.g. `foo(123) // returns number`, `foo("apple") // returns string` – JDB Aug 24 '22 at 23:38
  • I appreciate your help and while I understand your examples, I'm failing to understand how it applies to my question. I've added some more context to my question if you don't mind taking a look. – user1032752 Aug 25 '22 at 17:16