Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spread operator with React Props in Typescript (error: ...could be instantiated with a different subtype of constraint {})

I am trying to write a Higher Order Component in React using Typescript that receives the props, 'consumes' one of them and then passes on the remainder to the Child component.

function testConnect<T>(Child: React.ComponentType<T>): React.ComponentType<T> {

  type InitialState = {
    iS: StoreShape.State;
  };

  type LAP = InitialState & T;

  const Connector = (props: LAP) => {
    const { iS, ...rest } = props;
    // do something with iS
    return (
      <Child // Visual Studio complains about this line.
        {...rest}
      />
    );
  };

  return Connector;
}

However this fails with the error: 'Pick<LAP, Exclude<keyof T, "iS">>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'

I would like to know not only if there's anything I can do about it, but also why this happens at all.

like image 368
Abulafia Avatar asked Feb 21 '20 17:02

Abulafia


2 Answers

Actually, Typescript is catching a Problem with your component:

 const Component = testConnect(({ iS }) => <div>{iS}</div>);

 <Component iS={...} />

So you either have to

(a) pass on all props (instead of just rest) to the component.

   <Child   {...props} />

(b) Make sure that no prop named "iS" can be passed in by excluding the key iS from T:

 testConnect<T>(Child: React.ComponentType<Omit<T, "iS">>>): React.ComponentType<T> {
like image 100
Jonas Wilms Avatar answered Sep 30 '22 19:09

Jonas Wilms


Here is a solution to your problem, not the most elegant one, but it stops to complain:

type InitialState = {
    iS: StoreShape.State;
};

function testConnect<T>(Child: React.ComponentType<T>): React.ComponentType<T> {
    const Connector = (props: InitialState & Exclude<T, "iS">) => {
        const isT = (object: any): object is T => {
            return !("iS" in object);
        };

        const { iS, ...rest } = props;

        if (isT(rest)) {
            return (
                <Child // Visual Studio complains on this line.
                    {...rest}
                />
            );
        }

        return <Child {...props} />;
    };

    return Connector;
}

To your code stops to complain, you need to fix how you are passing the props to the Child component. He accepts props of type T. But rest in your case is not of type T (or at least TS compiler is not smart enough to figure it out), its type is Pick<LAP, Exclude<keyof T, "iS">> because of the rest operator you used.

My solution idea is just to let the compiler knows that rest is of type T, using a custom type guard function, but you could use other approaches, like a type casting:

<Child
  {...((rest as unknown) as T)}
/>

I hope it helps you!

like image 38
Pedro Mutter Avatar answered Sep 30 '22 19:09

Pedro Mutter