Reputation: 3673
Given the following typed React component using generic type arguments, how would I go about wrapping it in React's new forwardRef
API?
type Props<T> = {
forwardedRef?: Ref<HTMLInputElement>
...
}
class GenericComponent<T> extends Component<Props<T>> {
...
}
const ComponentWithRef = forwardRef<HTMLInputElement, Props<T>>((props, ref) => (
<StringInput<T> {...props} forwardedRef={ref} />
))
The above approach has no way to define the T
generic.
Upvotes: 25
Views: 18113
Reputation:
I think there is a workaround:
type Props<T> = {
ref?: MutableRefObject<HTMLDivElement | null>;
someProp: string;
testType: T;
}
const TestComponent: <T extends any>(props: Props<T>) => ReactNode = (() => {
// eslint-disable-next-line react/display-name
return forwardRef(({someProp, testType}, ref: ForwardedRef<HTMLDivElement>) => {
console.log(someProp, testType);
return <div ref={ref}>testasd</div>;
});
})();
and then:
type TestType = {
name: string;
};
const SomePage: FC = () => {
const someRef = useRef<HTMLDivElement | null>(null);
return (
<div>
<TestComponent<TestType> someProp='OK' ref={someRef} testType={{name: 'test'}}/>
</div>
);
};
this compiles with no errors.
the type of testType
is inferred correctly
the following gets an error:
<TestComponent<TestType> someProp='ERROR' ref={someRef} testType={'test'}/>
Type 'string' is not assignable to type 'TestType'.ts(2322)
which is expected.
Upvotes: 1
Reputation: 3673
So, to broaden the question some, this is really a question about preserving generic types in higher order functions. The following usage of forwardRef
will properly typecheck (in 3.0.1
)
const SelectWithRef = forwardRef(<Option extends string>(props: Props<Option>, ref?: Ref<HTMLSelectElement>) =>
<Select<Option> {...props} forwardedRef={ref} />);
But, the Option
generic is immediately resolved to string
, rather than remaining as a generic. As such, the following does not typecheck
const onChange: (value: 'one' | 'two') => void = (value) => console.log(value);
<SelectWithRef<'one' | 'two'>
^^^^^^^^^^^^^^^ [ts] Expected 0 type arguments, but got 1
value="a"
options={['one', 'two']}
onChange={onChange}
^^^^^^^^^^ [ts] Type 'string' is not assignable to type '"one" | "two"'
/>
The relevant issue is tracked in this Typescript issue ticket.
Upvotes: 27