1

I have this Higher order component written down:

type WrappedComponentConditionallyHidden =
  <P>(WrappedComponent: React.ComponentType<P>) => React.FC<P & { isHidden: boolean }>;
    
const hideConditional: WrappedComponentConditionallyHidden = (WrappedComponent) => {

  return (props) => {
    // typeof props is inferred here correctly: P & { isHidden: boolean }        

    // Don't want to pass down "isHidden"
    const { isHidden, ...passedProps } = props; 

    if (props.isHidden) {
      return null;
    } else {
      return <WrappedComponent {...passedProps} /> 
      // Passing { ...props } works but ^^^^ this fails
    }
  }
}

I get this Typescript error:

 Type 'Pick<PropsWithChildren<P & { isHidden: boolean; }>, "children" | Exclude<keyof P, "isHidden">>' is not assignable to type 'IntrinsicAttributes & P & { children?: ReactNode; }'
  Type 'Pick<PropsWithChildren<P & { isHidden: boolean; }>, "children" | Exclude<keyof P, "isHidden">>' is not assignable to type 'P'.
    'Pick<PropsWithChildren<P & { isHidden: boolean; }>, "children" | Exclude<keyof P, "isHidden">>' is assignable to the constraint of type 'P', but 'P' could be instantiated with a different subtype of constraint '{}'.

For me, 'Pick<PropsWithChildren<P & { isHidden: boolean; }>, "children" | Exclude<keyof P, "isHidden">> should be equal to PropsWithChildren<P> and hence it should work. Any ideas why it wouldn't?

Hassan Naqvi
  • 933
  • 5
  • 18
  • Sounds like the compiler's inference is getting confused about the types. I don't know if this will work, but you could try offering it some help: `const { isHidden, ...passedProps }: { isHidden: boolean, passedProps: PropsWithChildren

    } = props;`. At the very least you might get a more helpful compiler error telling you why the spread variable can't be cast.

    – JDB Jun 15 '21 at 16:30

1 Answers1

1

The general problem is explained in great details in this answer. And if you're looking for more grounded example of this problem you may look for this answer.

And I believe the most clear explanation is done here:

TS isn't capable of the higher-order reasoning needed to understand that Exclude<T, k> & { [k]: T[k] } is equivalent to T. You can only make that determination through understanding what Exclude and & actually do at a higher level, but from TS's point of view, Exclude is just a type alias

As a solutions you may just hint TS about the real type of passedProps:

const hideConditional = function <P>(WrappedComponent: React.ComponentType<P>): React.FC<P & { isHidden: boolean }> {
  return (props) => {
    const { isHidden, ...passedProps } = props; 

    if (props.isHidden) {
      return null;
    } else {
      return <WrappedComponent {...passedProps as P} /> 
    }
  }
}

playground link

aleksxor
  • 7,535
  • 1
  • 22
  • 27