Etheryte
Etheryte

Reputation: 25310

How to type higher order component that provides props

I have a sample placeholder component that consumes two required props:

type ComponentProps = {
  foo: string;
  bar: number;
}

const Component = (props: ComponentProps) => <></>;

I'm trying to write a higher order component that provides some certain props, and thus removes them from the required props type for further use:

type WrapperProvides = {
  foo: string;
}

function withFoo<T extends WrapperProvides>(ToWrap: (componentProps: T) => JSX.Element) {
  type RemainingProps = Omit<T, keyof WrapperProvides>;
  const providedFoo = "foo";
  return (props: RemainingProps) => <ToWrap foo={providedFoo} {...props} />;
}

This would then be used elsewhere without needing to specify foo:

const WrappedComponent = withFoo(Component);
const result = <WrappedComponent bar={42} />;

However, currently the return of withFoo() yields the following error on <ToWrap .../>:

Type '{ foo: string; } & Pick<T, Exclude<keyof T, "foo">>' is not assignable to type 'IntrinsicAttributes & T'.
  Type '{ foo: string; } & Pick<T, Exclude<keyof T, "foo">>' is not assignable to type 'T'.
    '{ foo: string; } & Pick<T, Exclude<keyof T, "foo">>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'WrapperProvides'.(2322)

Functionality-wise, the code already works as expected, but I can't figure out what the type issue is here.
I've looked through related issues on "could be instantiated with a different subtype of constraint", but I feel I don't understand the problem conceptually.

A Typescript playground replicating the issue can be found here.

Upvotes: 1

Views: 90

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249486

TS will not be able to follow that WrapperProvides & Omit<T, keyof WrapperProvides> is the same as T. You can either use a type assertion

function withFoo<T extends WrapperProvides>(ToWrap: (componentProps: T) => JSX.Element) {
  type RemainingProps = Omit<T, keyof WrapperProvides>;
  const providedFoo = "foo";
  return (props: RemainingProps) => <ToWrap foo={providedFoo} {...props as any} />;
}

Playground Link

Or you could add a redundant type to the parameter of the component (WrapperProvides & Omit<T, keyof WrapperProvides>). This type will ensure compatibility with the specified properties in your function, and should not have major drawbacks on the calling side as T and WrapperProvides & Omit<T, keyof WrapperProvides> should evaluate to the same type (or at least compatible types) on the calling side where T is known

function withFoo<T extends WrapperProvides>(ToWrap: (componentProps: T | (WrapperProvides & Omit<T, keyof WrapperProvides>)) => JSX.Element) {
  type RemainingProps = Omit<T, keyof WrapperProvides>;
  const providedFoo = "foo";
  return (props: RemainingProps) => <ToWrap foo={providedFoo} {...props} />;
}

Playground Link

Upvotes: 2

Related Questions