[javascript] Removing element from array in component state

I am trying to find the best way to remove an element from an array in the state of a component. Since I should not modify the this.state variable directly, is there a better way (more concise) to remove an element from an array than what I have here?:

  onRemovePerson: function(index) {
    this.setState(prevState => { // pass callback in setState to avoid race condition
      let newData = prevState.data.slice() //copy array from prevState
      newData.splice(index, 1) // remove element
      return {data: newData} // update state
    })
  },

Thank you.

updated

This has been updated to use the callback in setState. This should be done when referencing the current state while updating it.

This question is related to javascript reactjs

The answer is


The cleanest way to do this that I've seen is with filter:

removeItem(index) {
  this.setState({
    data: this.state.data.filter((_, i) => i !== index)
  });
}

You could make the code more readable with a one line helper function:

const removeElement = (arr, i) => [...arr.slice(0, i), ...arr.slice(i+1)];

then use it like so:

this.setState(state => ({ places: removeElement(state.places, index) }));

You could use the update() immutability helper from react-addons-update, which effectively does the same thing under the hood, but what you're doing is fine.

this.setState(prevState => ({
  data: update(prevState.data, {$splice: [[index, 1]]})
}))

Here is a simple way to do it:

removeFunction(key){
  const data = {...this.state.data}; //Duplicate state.
  delete data[key];                  //remove Item form stateCopy.
  this.setState({data});             //Set state as the modify one.
}

Hope it Helps!!!


You can use this function, if you want to remove the element (without index)

removeItem(item) {
  this.setState(prevState => {
    data: prevState.data.filter(i => i !== item)
  });
}

As mentioned in a comment to ephrion's answer above, filter() can be slow, especially with large arrays, as it loops to look for an index that appears to have been determined already. This is a clean, but inefficient solution.

As an alternative one can simply 'slice' out the desired element and concatenate the fragments.

var dummyArray = [];    
this.setState({data: dummyArray.concat(this.state.data.slice(0, index), this.state.data.slice(index))})

Hope this helps!


I want to chime in here even though this question has already been answered correctly by @pscl in case anyone else runs into the same issue I did. Out of the 4 methods give I chose to use the es6 syntax with arrow functions due to it's conciseness and lack of dependence on external libraries:

Using Array.prototype.filter with ES6 Arrow Functions

removeItem(index) {
  this.setState((prevState) => ({
    data: prevState.data.filter((_, i) => i != index)
  }));
}

As you can see I made a slight modification to ignore the type of index (!== to !=) because in my case I was retrieving the index from a string field.

Another helpful point if you're seeing weird behavior when removing an element on the client side is to NEVER use the index of an array as the key for the element:

// bad
{content.map((content, index) =>
  <p key={index}>{content.Content}</p>
)}

When React diffs with the virtual DOM on a change, it will look at the keys to determine what has changed. So if you're using indices and there is one less in the array, it will remove the last one. Instead, use the id's of the content as keys, like this.

// good
{content.map(content =>
  <p key={content.id}>{content.Content}</p>
)}

The above is an excerpt from this answer from a related post.

Happy Coding Everyone!


Here is a way to remove the element from the array in the state using ES6 spread syntax.

onRemovePerson: (index) => {
  const data = this.state.data;
  this.setState({ 
    data: [...data.slice(0,index), ...data.slice(index+1)]
  });
}

I believe referencing this.state inside of setState() is discouraged (State Updates May Be Asynchronous).

The docs recommend using setState() with a callback function so that prevState is passed in at runtime when the update occurs. So this is how it would look:

Using Array.prototype.filter without ES6

removeItem : function(index) {
  this.setState(function(prevState){
    return { data : prevState.data.filter(function(val, i) {
      return i !== index;
    })};
  });
}

Using Array.prototype.filter with ES6 Arrow Functions

removeItem(index) {
  this.setState((prevState) => ({
    data: prevState.data.filter((_, i) => i !== index)
  }));
}

Using immutability-helper

import update from 'immutability-helper'
...
removeItem(index) {
  this.setState((prevState) => ({
    data: update(prevState.data, {$splice: [[index, 1]]})
  }))
}

Using Spread

function removeItem(index) {
  this.setState((prevState) => ({
    data: [...prevState.data.slice(0,index), ...prevState.data.slice(index+1)]
  }))
}

Note that in each instance, regardless of the technique used, this.setState() is passed a callback, not an object reference to the old this.state;


Just a suggestion,in your code instead of using let newData = prevState.data you could use spread which is introduced in ES6 that is you can uselet newData = ...prevState.data for copying array

Three dots ... represents Spread Operators or Rest Parameters,

It allows an array expression or string or anything which can be iterating to be expanded in places where zero or more arguments for function calls or elements for array are expected.

Additionally you can delete item from array with following

onRemovePerson: function(index) {
  this.setState((prevState) => ({
    data: [...prevState.data.slice(0,index), ...prevState.data.slice(index+1)]
  }))
}

Hope this contributes!!