'react-select doesn't keep indexes on manipulated arrays

In an array of react-selects, any input before the last one will break indexing for the following ones. I know about uuid for key (which is not recommended) and also about Math.floor(Math.random() * 10000) for key. But I still can't get this to work.

The key generated with Math.floor(Math.random() * 10000) breaks the input: you can't populate anything into the field.

It's an indexing problem and I believe it's a problem from within the plugin. I need to clarify this isn't the issue hopefully.

I've created a codesandbox with Math.floor(Math.random() * 10000) as key so you'd see.

https://codesandbox.io/s/codesandboxer-example-forked-dcvdj?file=/example.tsx

Add on at least one input and try to populate. It won't let you.

Put index in key and you'll see you can populate, but when you try to delete it, the value stays the same and instead the one under that one gets deleted (indexing issue, I know). This is because when state is updated, map will run again and will reindex the list once again.



Solution 1:[1]

One solution would be to reference you AsyncSelect values into an array like so:

state = {
    inputValue: "",
    fieldsArr: [],
    values: []
  };

Consequently your render() function would look like:

render() {
    return (
      <div>
        <pre>inputValue: "{this.state.inputValue}"</pre>
        {this.state.fieldsArr.map((item, index) => (
          <div key={index}>
            <span>{item}</span>
            <AsyncSelect
              cacheOptions
              loadOptions={this.loadOptions}
              defaultOptions
              value={this.state.values[index]} // <--- This will reference your state value
              onChange={(e) => this.handleChange(e, index)}
              onInputChange={this.handleInputChange}
            />
            <button onClick={() => this.handleDelete(index)}>delete</button>
          </div>
        ))}
        <button onClick={this.handleAdd}>add</button>
      </div>
    );
  }

Then you will add an handleChange callback to handle the AsyncSelect onChange event.


handleChange(el, index) {
    let vals = this.state.values;
    vals[index] = el;
    this.setState((prev) => ({ ...prev, values: vals }));
  }

And finally edit the handleDelete and handleAdd functions accordigly:

handleAdd = () => {
    let newArr = this.state.fieldsArr;
    let values = this.state.values;
    newArr.push(this.state.fieldsArr.length + 1);
    values.push("");
    this.setState((prev) => ({ ...prev, fieldsArr: newArr }));
  };

  handleDelete = (index) => {
    let newArr = this.state.fieldsArr;
    let values = this.state.values;
    newArr.splice(index, 1);
    values.splice(index, 1);
    this.setState((prev) => ({ ...prev, fieldsArr: newArr, values }));
  };

Solution 2:[2]

My solution was to create a counter, add to that counter on add field, then on delete using indexOf item to delete.

https://codesandbox.io/s/codesandboxer-example-forked-dcvdj?file=/example.tsx

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Antonio Della Fortuna
Solution 2 Manny Alvarado