14

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;
};
2
  • Could you paste your typescript version and @types/react version? It seems to work for me but probably doing sth differently. BTW it actually complains about this undefined part so that e is optional. Commented Dec 27, 2018 at 11:15
  • @barnski typescript - v3.2.1 / @types/react - v16.7.6 Commented Dec 27, 2018 at 14:54

2 Answers 2

13

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} />
        );
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Was too long, had to add as answer
It worked with a few adjustments. It will fail whenever I try to destructure props. Thanks!
@JCQuintas yeah distructuring will not work as expected because of the union.. that is why I removed it. (Delete the answer with the error, it will get downvoted otherwise, with good cause)
3

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

3 Comments

Hm .. what is your definition of IInputProps ? The code you provide has the same error in Usage
TypeScript V3.2.2
your code works with strict:false, but OP's code would have worked with strict off as well... I'm guessing strictFunctionTypesis what is causing the error on the assignment of onChange

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.