2
votes

In JSX/TSX syntax from react, all inputs seems to be define with the same props declaration which is InputHTMLAttributes:

    interface InputHTMLAttributes<T> extends HTMLAttributes<T> {
        accept?: string;
        alt?: string;
        autoComplete?: string;
        autoFocus?: boolean;
        capture?: boolean | string; // https://www.w3.org/TR/html-media-capture/#the-capture-attribute
        checked?: boolean;
        crossOrigin?: string;
        disabled?: boolean;
        form?: string;
        formAction?: string;
        formEncType?: string;
        formMethod?: string;
        formNoValidate?: boolean;
        formTarget?: string;
        height?: number | string;
        list?: string;
        max?: number | string;
        maxLength?: number;
        min?: number | string;
        minLength?: number;
        multiple?: boolean;
        name?: string;
        pattern?: string;
        placeholder?: string;
        readOnly?: boolean;
        required?: boolean;
        size?: number;
        src?: string;
        step?: number | string;
        type?: string;
        value?: string | ReadonlyArray<string> | number;
        width?: number | string;

        onChange?: ChangeEventHandler<T>;
    }

I want to define a specific type for only an input of type number so that TypeScript complains if I use a checked or disabled prop on it:

<!-- invalid `checked` prop -->
<input type="number" step="any" min="0" max="100" value="22.33" checked={true} />

So is there already a type definition for that coming from React?

I know that I can just define it like the following but I would prefer to use an official source:

interface InputNumberProps {
  type: 'number';
  max?: number | string;
  min?: number | string;
  ...
}
2
interface InputNumberProps<T> extends interface InputHTMLAttributes<T> { type, value ....}Robert

2 Answers

1
votes

I think the simplest solution would be to wrap input into your own component and provide custom type definitions. As far as I know, there is nothing like that coming from official React types. This is done because HTML itself is not strongly typed and it is valid to do something like <input type="number" checked />

As for type definition something like that would suffice:

interface NumberInputProps {
  type: "number"
  value: number
  min?: number
  max?: number
}

interface TextInputProps {
  type?: "text"
  value: string
}

interface CheckboxInputProps {
  type: "checkbox"
  checked?: boolean
}

type InputProps = NumberInputProps | TextInputProps | CheckboxInputProps

const Input: React.FC<InputProps> = (props) => <input {...props} />

That is a lot of typings for sure (and this does not event include things like events, class names etc). Also, this requires even more JS code if you want to provide stronger types for something like number fields (converting strings into numbers etc.)

If you want to modify the original InputHTMLAttributes (which is a better solution than writing your own types from scratch) interface, overriding and/or omitting some of the fields there are helper types like Omit that would allow you to do so.

type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "checked" | "min" | "max"> & (NumberInputProps | TextInputProps | CheckboxInputProps)

You can also split different input types into different components if you find writing too much logic in one component. Do whatever suits you

P.S. on the second note. This sounds like type over-engeneering

1
votes

You can use and extends the official types:

export type InputProps = React.DetailedHTMLProps<
  React.InputHTMLAttributes<HTMLInputElement>,
  HTMLInputElement
>;

interface NumberInputProps extends Omit<InputProps, 'checked' | 'disabled'> {
  value: number
}

Using this code you exclude checked and disabled properties and overrides value to allow only numbers instead string | ReadonlyArray | number