Reputation: 779
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?
Upvotes: 0
Views: 838
Reputation: 11
I've tried your solution, but I have some problems when returning an input text or a textarea
function MyComponent<T extends 'input' | 'textarea'>(props: InputProps<T>) {
const {inputType, ...rest} = props;
if (inputType === "input") {
return <input
// ^error
type={"text"}
{...rest}
/>
}
if (inputType === "textarea") {
return <textarea
// ^error
{...rest}
/>
}
return null
}
This are the returned errors:
Type '{ type: "text"; } & Omit<InputProps<T>, "inputType">' is not assignable to type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'.
Type '{ type: "text"; } & Omit<InputProps<T>, "inputType">' is not assignable to type 'ClassAttributes<HTMLInputElement>'.
Types of property 'ref' are incompatible.
Type 'InputProps<T>["ref"] | undefined' is not assignable to type 'LegacyRef<HTMLInputElement> | undefined'.
I can solve the problem if the rest
variable is casted to JSX.IntrinsicElements["input"]
or JSX.IntrinsicElements["textarea"]
... but I don't think is the best solution... Any alternative?
playground solved casting the rest variable
Upvotes: 0
Reputation: 187312
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
Upvotes: 2