mpen
mpen

Reputation: 282825

How to make higher-order component generic?

I can't figure out the syntax to make this component generic. The signature is:

const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select({onChange,options: _options,value,...props}, forwardedRef) {
   ...
}

But SelectProps is actually generic:

type SelectProps<T> = { ... }

So the type of Select should be:

type SelectComponent<T> = ForwardRefExoticComponent<PropsWithoutRef<SelectProps<T>> & RefAttributes<HTMLSelectElement>>

(That's from the return value of forwardRef)

But I can't figure out how to add the <T> to const Select = ...

This doesn't work:

const Select: SelectComponent = forwardRef...

Because "TS2314: Generic type 'SelectComponent' requires 1 type argument(s)." but I don't want to specify the type argument, I want the caller to specify it.

How do I type this?

Upvotes: 1

Views: 277

Answers (1)

jered
jered

Reputation: 11571

The simplest thing is to cast the result of forwardRef() back into its original form before being wrapped. This requires you to refactor a bit to pull out the inner component and make it fully generic:

type SelectProps<T> = {
  onChange: (newValue: T) => null;
  options: any;
  value: T;
};

function SelectInner<T>(
  { onChange, options: _options, value, ...props }: SelectProps<T>,
  forwardedRef: React.ForwardedRef<HTMLSelectElement>
) {
  return null;
}

const Select = forwardRef(SelectInner) as typeof SelectInner;

Another option is to augment the forwardRef type definition to be more what you want, which is perhaps more useful if you intend to be doing this a lot. If you look closely, this is essentially the same thing as the option above, just defined in a way such that it applies to all forwardRef() instances.

declare module 'react' {
  function forwardRef<T, P = {}>(
    render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
  ): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
}

type SelectProps<T> = {
  onChange: (newValue: T) => null;
  options: any;
  value: T;
};

const Select = forwardRef(
  (
    { onChange, options: _options, value, ...props }: SelectProps<T>,
    forwardedRef: React.ForwardedRef<HTMLSelectElement>
  ) => {
    return null;
  }
);

Note: in either case using React.ForwardedRef requires the latest @types/react to be installed. Source

Upvotes: 1

Related Questions