Asking
Asking

Reputation: 4212

Add Typescript types to react-select onChange function

I am using the CreatableSelect component from React Select in my application but I am having trouble adding Typescript types.

All I want to do is to change any from handleChange = (value: any) with the right types. I used this const handleChange = (value: { label: any; value: any }[]) => { but it does not work.

How can I change any type to the right type?

demo: https://codesandbox.io/s/codesandboxer-example-forked-9tsfp?file=/example.js

export default class CreatableInputOnly extends Component<*, State> {
  state = {
    inputValue: "",
    value: []
  };
  handleChange = (value: any) => {
    console.log(value, "test");
    this.setState({ value });
  };
  handleInputChange = (inputValue: string) => {
    this.setState({ inputValue });
  };
  handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
    const { inputValue, value } = this.state;
    if (!inputValue) return;
    switch (event.key) {
      case "Enter":
      case "Tab":
        console.group("Value Added");
        console.log(value);
        console.groupEnd();
        this.setState({
          inputValue: "",
          value: [...value, createOption(inputValue)]
        });
        event.preventDefault();
    }
  };
  render() {
    const { inputValue, value } = this.state;
    return (
      <CreatableSelect
        components={components}
        inputValue={inputValue}
        isClearable
        isMulti
        menuIsOpen={false}
        onChange={this.handleChange}
        onInputChange={this.handleInputChange}
        onKeyDown={this.handleKeyDown}
        placeholder="Type something and press enter..."
        value={value}
      />
    );
  }
}

Upvotes: 0

Views: 8717

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42298

The specific types depend on a generic type parameter OptionType. The default type for OptionType is the { label: string; value: string } type which you are using.

I believe that the issue you are having is due to incorrect/outdated type packages in your environment.


In a new CodeSandbox, where I installed @types/react-select, the definition for the onChange function is:

onChange?: (value: ValueType<OptionType, IsMulti>, action: ActionMeta<OptionType>) => void;

This ValueType utility type basically says that on a single select you have either an option or null and on on multiple select you have an array of options.

export type ValueType<OptionType extends OptionTypeBase, IsMulti extends boolean> = IsMulti extends true
    ? OptionsType<OptionType>
    : OptionType | null;

export type OptionsType<OptionType extends OptionTypeBase> = ReadonlyArray<OptionType>;

I'm not sure how your linked sandbox is enqueing its types because the @types/react-select package is not included in the package.json. But if I "Go To Definition" I get a definition from a /sandbox/node_modules/@types/react-select/src file. These are the types that I see:

onChange?: (value: ValueType<OptionType>, action: ActionMeta) => void;

export type ValueType<OptionType extends OptionTypeBase> = OptionType | OptionsType<OptionType> | null | undefined;

export type OptionsType<OptionType extends OptionTypeBase> = ReadonlyArray<OptionType>;

So what's the difference?

In the first case, we know that since you have a multi-select you will always receive an array of options.

In the second case there is no IsMulti flag, so the types state that you could receive an array or a single option (or null or undefined). Your handleChange function can only handle an array, so it can't be used as an onChange handler since it can't handle all of the possible arguments that it might receive.


With the correct package types, what you tried before is almost correct. The only issue is that the array is readonly, so we have to make sure that we can accept a ReadonlyArray in both the handler and the state.

FYI these functions aren't being called where I would intuitively expect. handleChange is called when removing a tag but not when adding a new one by pressing enter. So you'll want to check out the docs and/or play with the various callbacks. There are six props which are only on CreatableSelect -- two settings and four callbacks.

This should fix all of your Typescript errors, using @types/react-select 4.0.13 and react-select 4.3.0:

import React, { Component } from "react";
import CreatableSelect from "react-select/creatable";
import { ActionMeta, InputActionMeta } from "react-select";

const components = {
  DropdownIndicator: null
};

const createOption = (label: string) => ({
  label,
  value: label
});

type MyOption = ReturnType<typeof createOption>;
// or type MyOption = { label: string;  value: string; }

interface MyState {
  inputValue: string;
  value: ReadonlyArray<MyOption>;
}

interface MyProps {
  // are there any?
}

export default class CreatableInputOnly extends Component<MyProps, MyState> {
  state: MyState = {
    inputValue: "",
    value: []
  };
  handleChange = (
    value: ReadonlyArray<MyOption>,
    meta: ActionMeta<MyOption>
  ) => {
    console.log("value", value, "meta", meta);
    this.setState({ value });
  };
  handleInputChange = (inputValue: string, meta: InputActionMeta) => {
    this.setState({ inputValue });
  };
  handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
    const { inputValue, value } = this.state;
    if (!inputValue) return;
    switch (event.key) {
      case "Enter":
      case "Tab":
        console.group("Value Added");
        console.log(value);
        console.groupEnd();
        this.setState({
          inputValue: "",
          value: [...value, createOption(inputValue)]
        });
        event.preventDefault();
    }
  };
  render() {
    const { inputValue, value } = this.state;
    return (
      <CreatableSelect
        components={components}
        inputValue={inputValue}
        isClearable
        isMulti
        menuIsOpen={false}
        onChange={this.handleChange}
        onInputChange={this.handleInputChange}
        onKeyDown={this.handleKeyDown}
        placeholder="Type something and press enter..."
        value={value}
      />
    );
  }
}

Code Sandbox Link

Upvotes: 3

Related Questions