In my React (w/ typescript) application I've created a form using react-hook-form to manage all the logic of it.
I've then customised the select element with some css and other things. But, for the sake of question's simplicity, here the barebone component:
import { forwardRef } from 'react';
type Props = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>;
const Select = forwardRef<HTMLSelectElement, Props>((props, ref) => {
return (
<select ref={ref} {...props}>
<option value="">...</option>
{props.children}
</select>
);
});
export default Select;
I've then defined another component that "specialise" the previous one, as:
import { forwardRef } from 'react';
import Select from './select';
type Props = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement> & { label: string; errors?: string };
const SelectWitLargeData = forwardRef<HTMLSelectElement, Props>((props, ref) => {
return (
<Select ref={ref} {...props}>
...large amount of options
</Select>
);
});
export default SelectWitLargeData;
The problem is that the compiler gives me an error on <Select ref={ref} (<- here) ...>
Type 'ForwardedRef<HTMLSelectElement> | LegacyRef<HTMLSelectElement>' is not assignable to type 'Ref<HTMLSelectElement>'.
Type 'string' is not assignable to type 'Ref<HTMLSelectElement>'.ts(2322)
index.d.ts(140, 9): The expected type comes from property 'ref' which is declared here on
type 'IntrinsicAttributes & Pick<Props, "label" | "key" | keyof InputHTMLAttributes<HTMLSelectElement> | "errors"> & RefAttributes<...>'
(JSX attribute) RefAttributes<HTMLSelectElement>.ref?: Ref<HTMLSelectElement>
I've search for solutions online, and found many suggestions, but none of these work.
Any help is appreciated Thanks in advance!
Edit Here's an example form to consume the components
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import Select from '@components/select';
import SelectWithLargeData from '@components/select-with-large-data';
type Form = {
email: string;
state: string;
country: string;
};
const Address: React.FC = () => {
const { handleSubmit, register, setValue } = useForm<Form>();
useEffect(() => {
setValue('state', 'AL');
}, [setValue]);
const onSubmit = async (input: Form) => {
console.log(input);
};
return (
<>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-10">
<input type="email" {...register('email', { required: true })} />
<SelectWithLargeData label="Provincia" {...register('state', { required: true })} />
<Select label="Stato" {...register('country', { required: true })}>
<option value="IT">Italia</option>
</Select>
</form>
</>
);
};
export default Address;
Edit 2 https://codesandbox.io/s/withered-rain-w9ej4
As the edit queue is full, I'll add a separate answer. With this you can actually get the ref from HTML select element all the way up to the form. Is this what you are looking for?
import React, { forwardRef } from 'react';
type Props = Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>, 'ref'>;
const Select = forwardRef<HTMLSelectElement, Props>((props, ref) => (
<select {...props} ref={ref}>
<option value="">...</option>
{props.children}
</select>
)
);
type SelectHigherOrder = Props & { label: string; errors?: string };
const SelectWithLargeData = forwardRef<HTMLSelectElement, SelectHigherOrder>((props, ref) => (
<Select {...props} ref={ref} >
...large amount of options
</Select>
);
Also I would recommend to spread the props first and then add your own props, in order to avoid overwriting.
Please see forwarding-refs docs:
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
You should create ref inside SelectWitLargeData instead of forwarding it again.
Consider this example:
import React, { forwardRef } from 'react';
type Props = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>;
const Select = forwardRef<HTMLSelectElement, Props>((props, ref) => {
return (
<select ref={ref} {...props}>
<option value="">...</option>
{props.children}
</select>
);
});
type SelectHigherOrder = Props & { label: string; errors?: string };
const SelectWitLargeData = (props: Omit<SelectHigherOrder, 'ref'>) => {
const ref = React.useRef<HTMLSelectElement>(null);
return (
<Select ref={ref} {...props}>
1
</Select>
);
}
Playground
You probably might have noticed that I have used Omit<SelectHigherOrder, 'ref'>. This is because you are using this type: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>;. And this type contains legacy ref type: type WithLegacyRef = Props['ref'] and when you use {...props} after ref={ref} you are actually overriding correct ref type which is React.RefObject<HTMLSelectElement>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With