0

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: errors

What's the right way to go about resolving this?

0

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

1 Comment

Hey @Alex - just wanted to say thanks for taking the time to write such a complete and in depth solution, it's very much appreciated! :D
0

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'.

playground with error

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.