Search code examples
reactjstypescripthigher-order-components

Loading higher-order component


I wrote this higher-order component (HOC):

function withLoading<T>(WrappedComponent: ComponentType<T>) {
  return function ({ isLoading, ...props }: { isLoading: boolean } & T) {
    return isLoading ? <p>Loading…</p> : <WrappedComponent {...props} />;
  };
}

But TypeScript complains about <WrappedComponent {...props} />:

Type 'Omit<{ isLoading: boolean; } & T, "isLoading">' is not assignable to type 'IntrinsicAttributes & T'.
  Type 'Omit<{ isLoading: boolean; } & T, "isLoading">' is not assignable to type 'T'.
    'T' could be instantiated with an arbitrary type which could be unrelated to 'Omit<{ isLoading: boolean; } & T, "isLoading">'.(2322)

What am I doing wrong?


Solution

  • Omit<{ isLoading: boolean } & T, 'isLoading'> is not assignable to T because it is not a subtype of T; it is a supertype of T since we omit a property. See this answer.

    When T has no { isLoading: boolean } property, Omit<{ isLoading: boolean } & T, 'isLoading'> is equal to T (here Y is true):

    type X<T> = Omit<{ isLoading: boolean } & T, 'isLoading'>;
    type Y = X<{}> extends {} ? true : false;
    

    But when T has a { isLoading: boolean } property, Omit<{ isLoading: boolean } & T, 'isLoading'> is not equal to T (here Y is false):

    type X<T> = Omit<{ isLoading: boolean } & T, 'isLoading'>;
    type Y = X<{ isLoading: boolean }> extends { isLoading: boolean } ? true : false;
    

    So for our use case, instead of assigning the Omit<{ isLoading: boolean } & T, 'isLoading'> supertype to T, we should assign the { isLoading: boolean } & T subtype to T:

    function withLoading<T>(WrappedComponent: ComponentType<T>) {
      return function (props: { isLoading: boolean } & T) {
        return props.isLoading ? <p>Loading…</p> : <WrappedComponent {...props} />;
      };
    }