JCQuintas
JCQuintas

Reputation: 1138

Typescript & React Component that accepts onChange for both TextArea and Input

I'm new to typescript and I am trying to create an input component that, if it receives type="text" it renders an input and if it receives type="textarea" it renders, you got it, a textarea. The problem is that typescript is complaining when I use the component on my code together with a onChange, it seems it wont allow me to use two types on the same event?

It shows me:

Type '(e: ChangeEvent<HTMLInputElement>) => void' is not assignable to type '(e?: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLTextAreaElement> | undefined) => void'.
Types of parameters 'e' and 'e' are incompatible.
Type 'ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLTextAreaElement> | undefined' is not assignable to type 'ChangeEvent<HTMLInputElement>'.
Type 'undefined' is not assignable to type 'ChangeEvent<HTMLInputElement>'.

input.js

interface InputProps {
  className?: string;
  type?: string;
  placeholder?: string;
  onChange?: (e?: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>) => void;
}

const Input: FunctionComponent<InputProps> = ({ type = 'text', ...props }) => {
  if (type === 'textarea') {
    return <textarea {...props} />;
  }
  return <input type={type} {...props} />;
};

usage:

class Usage extends React.Component<State> {
  state: State;

  onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({ input: e.target.value });
  };

  render() {
    return (
        <Input placeholder="Write an something..." onChange={this.onInputChange} />
    );
  }
}

How can I fix it?


UPDATE

The way I solved it for the moment is by saying that event can be of type any, but that's kind of a hack

type CommonProps = {
  className?: string;
  placeholder?: string;
  type?: string;
  onChange?: (e: any) => void;
};

Upvotes: 14

Views: 30753

Answers (2)

Harish Soni
Harish Soni

Reputation: 1896

You need to create a class incase you are using typescript. The normal function does not allows the | in the typescript prop types.

This should be your Input.js file:

export interface IInputProps {
  className?: string;
  type?: string;
  placeholder?: string;
  onChange?: (e?: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>) => void;
}

export class Input extends React.Component<IInputProps, {}> {
  constructor(props: IInputProps) {
    super(props);
  }
  render() {
    const { props, props: { type } } = this;
    if (type === 'textarea') {
      return <textarea {...props} />;
    }
    return <input type={type} {...props} />;
  }
}

and here is how it can be used:

class Usage extends React.Component<State> {
  state: State;

  onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({ input: e.target.value });
  };

  render() {
    return (
      <Input placeholder="Write an something..." onChange={this.onInputChange} />
    );
  }
}

This is how it will evaluate if it is a Input or a TextArea:

enter image description here

Upvotes: 3

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

You can use a discriminated union to pass in two types of arguments, one for text and the other for textarea. This has the added bonus of ensuring the handler and the type are in sync.

type InputProps = { // The common Part
    className?: string;
    placeholder?: string;
} & ({ // The discriminated union
    type?: "text";
    onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
} | {
    type: "textarea";
    onChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
})

const Input: FunctionComponent<InputProps> = (props: InputProps) => {
    if (props.type === 'textarea') {
        return <textarea {...props} />;
    }
    return <input {...props} />;
};


class Usage extends React.Component<State> {
    state: State;

    onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        this.setState({ input: e.target.value });
    };

    render() {
        return (
            <Input placeholder="Write an something..." onChange={this.onInputChange} />
        );
    }
}

Upvotes: 13

Related Questions