Typescript interface extends two component types
So this will output an <input>
element - everything works perfectly:
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
error?: FieldError;
icon?: string;
id: string;
register?: UseFormRegisterReturn;
}
const StyledInputComponent = styled.input`
...
`
const MyComponent = ({
error,
icon,
id,
register,
onChange,
...props
}: InputProps) => {
return (
<StyledInputComponent
hasError={!!error}
id={id}
onChange={onChange}
placeholder={placeholder}
type={type}
{...register}
{...props}
/>
});
};
I now need to be able to have the consuming component choose between an <input>
and a <textarea>
.
The problem I have is that (I think) I need to extend the interface further like this:
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement>, React.InputHTMLAttributes<HTMLTextAreaElement> {
error?: FieldError;
icon?: string;
id: string;
inputType?: "input" | "textarea";
register?: UseFormRegisterReturn;
}
And I'm just using the new inputType
prop to switch between outputted components:
{ inputType === "input" &&
<StyledInputComponent
hasError={!!error}
id={id}
onChange={onChange}
placeholder={placeholder}
type={type}
{...register}
{...props}
/>
}
{ inputType === "textarea" &&
<StyledTextAreaComponent
hasError={!!error}
id={id}
onChange={onChange}
placeholder={placeholder}
rows={4}
{...register}
{...props}
/>
}
However, trying to extend the interface for both an <input>
and a <textarea>
has lead to numerous errors, all along these lines:
What's the right way to go about resolving this?
Solution 1:
Your component cannot extend the props of input
AND textarea
because they have they both have properties with some of the same names, but different types.
So your component should only extends one of these, depending on what is passed as the inputType
.
That means you need generics here.
For example:
type InputProps<T extends 'input' | 'textarea'> = {
id: string;
inputType?: T;
} & JSX.IntrinsicElements[T]
Here JSX.InstrinsicElements['tagnamehere']
will return the props that react would allow for that tag name. And T
is set by the value of inputType
, which is either input
or textarea
.
Now you just need to make you component generic as well, and pass that generic to the props type:
function MyComponent<T extends 'input' | 'textarea'>(props: InputProps<T>) {
return <></>
}
Now to test it out:
// no type errors
const testInput = <MyComponent id="123" inputType='input' checked />
const testTextarea = <MyComponent id="456" inputType='textarea' rows={10} />
// type error, can't use input prop on a textarea
const testBadProps = <MyComponent id="456" inputType='textarea' checked />
// ^ type error
Playground