React HOC type inferring

282 Views Asked by At

I would like to create a higher-order function that returns a higher-order component.

In the example below I've created a validateSearchParams function, which creates a function which then can be used to wrap a component. However, Typescript (3.7.2) cannot infer the types correctly.

export function validateSearchParams<Props extends { location: Location }>(
  test: (searchParams: URLSearchParams) => boolean,
  redirectTo: string,
): (Cmp: React.ComponentType<Props>) => React.ComponentType<Props> {
  return (Cmp) => (props) => {
    const searchParams = new URLSearchParams(props.location.search);

    if (!test(searchParams)) {
      return <Redirect to={redirectTo} />;
    }

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

export const Test: React.FC<{ something: boolean; location: Location }> = () => null;

// This is OK
validateSearchParams<{ something: boolean; location: Location }>(
  (searchParams) => !!searchParams.get('foo'),
  'hxxp://somewhere',
)(Test);

// ...but without an explicit type it does not compile
validateSearchParams((searchParams) => !!searchParams.get('foo'), 'http://test.example')(Test);
                                                                                         ^^^^

In the last line I get a following error:

Argument of type 'FC<{ something: boolean; location: Location; }>' is not assignable to
parameter of type 'ComponentType<{ location: Location; }>'.
  Type 'FC<{ something: boolean; location: Location; }>' is not assignable to type
'FunctionComponent<{ location: Location; }>'.
    Types of parameters 'props' and 'props' are incompatible.
      Type 'PropsWithChildren<{ location: Location; }>' is not assignable to type
'PropsWithChildren<{ something: boolean; location: Location; }>'.
        Property 'something' is missing in type 'PropsWithChildren<{ location: Location; }>' but
required in type '{ something: boolean; location: Location; }'.ts(2345)

I am also able to create an easier version of a HOC, by incorporating the Cmp into parameters list:

export function validateSearchParams2<Props extends { location: Location; match: match<any> }>(
  Cmp: React.ComponentType<Props>,
  test: (searchParams: URLSearchParams) => boolean,
  redirectTo: string,
): React.ComponentType<Props> {
  return (props) => {
    const searchParams = new URLSearchParams(props.location.search);

    if (!test(searchParams)) {
      return <Redirect to={redirectTo} />;
    }

    return <Cmp {...props} />;
  };
}
export const Test: React.FC<{ something: boolean; location: Location }> = () => null;

validateSearchParams2(Test, (searchParams) => !!searchParams.get('foo'), 'hxxp://somewhere');

...but is there a way to make the first version of the validateSearchParams work without an explicit type?

1

There are 1 best solutions below

0
Linda Paiste On BEST ANSWER

Right now the generic type Props is set on the function validateSearchParams. You want to move the generic to to the returned function. Basically validateSearchParams is not a generic function, but it returns a generic HOC.

export function validateSearchParams(
  test: (searchParams: URLSearchParams) => boolean,
  redirectTo: string,
): <Props extends { location: Location }>(Cmp: React.ComponentType<Props>) => React.ComponentType<Props> {
...

Now you do not need to set an explicit type since it can be inferred from the component that you call it with.

Typescript Playground Link