141

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.

aherriot
  • 4,495
  • 6
  • 24
  • 36

11 Answers11

160

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)
  });
}
ephrion
  • 2,687
  • 1
  • 14
  • 17
  • 19
    @HussienK the `_` is sometimes used to represent an unused argument. Here, it's the current item in the array. – chrisM Jan 20 '16 at 16:28
  • 1
    Amazing. I'm using .filter all the time but never considered to use it in this situation. :D – dakt Oct 14 '16 at 11:49
  • 4
    This is clean code, however, for large arrays, this method is slow. Reason being, it loops through the entire array to find an index that looks to already be determined. – Matt Ellis Feb 14 '17 at 23:08
  • 1
    @MattEllis Since React's state updates are immutable anyway, you're going to be incurring O(n) in the size of the list to copy it in any case. I would be surprised if this were a significant performance hit. – ephrion Mar 31 '17 at 21:28
  • @ephrion The solution looks awesome. Unfortunately for me the last index and first index from the array are not deleting with this method. Do you any clue ? – Mo. Aug 26 '17 at 23:52
  • This is most simple and clean solution. – Asif Iqbal Dec 19 '17 at 14:55
  • 4
    Evaluating `this.state` as input to `this.setState()` , even immutably, is not recommended. please see my answer above for a reference to the official React docs on this topic. – pscl Apr 27 '18 at 18:50
  • I used immutability-helper as Rich Warrior suggests, but what is the reason you are suggesting filter instead of splice? – Onat Korucu Feb 18 '20 at 11:21
85

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]]})
}))
Lars Blumberg
  • 19,326
  • 11
  • 90
  • 127
Jonny Buchanan
  • 61,926
  • 17
  • 143
  • 150
  • Much simpler example than the one you linked to :) – Koen. Mar 21 '16 at 10:31
  • 8
    `react-addons-update` is now deprecated (2016). `immutability-helper` is available as a replacement. https://github.com/kolodny/immutability-helper Also please see my answer below regarding not mutating this.state directly inside this.setState(). – pscl Feb 14 '17 at 00:05
72

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;

Edgar
  • 6,022
  • 8
  • 33
  • 66
pscl
  • 3,322
  • 25
  • 29
  • 3
    This is the correct answer, see also [The Power of Not Mutating Data](https://facebook.github.io/react/docs/optimizing-performance.html#the-power-of-not-mutating-data) – Vinnie James Jan 21 '17 at 21:08
  • 1
    Examples using the prevState callback with various techniques added. – pscl Feb 09 '17 at 03:45
  • what if i do it like this? `this.setState({ data: [...this.state.data.slice(0, index), ...this.state.data.slice(index + 1)] });` is it wrong to use `this.state` instead of the `prevState` callback option showed by @user1628461? – Tiberiu Maxim May 25 '17 at 14:01
  • This is the best solution, although not simplest – Developia Jun 27 '17 at 19:32
  • thanks, using spread its possible to update an element in middle too instead of deleting it, that's what i needed. – Vaibhav Vishal Nov 17 '18 at 07:00
  • I am stuck with this same problem: https://stackoverflow.com/questions/74017672/state-not-getting-update-on-deleting-one-object-from-an-array-in-usestate Please have a look at this once – user2028 Oct 10 '22 at 16:36
26

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)]
  });
}
evianpring
  • 3,316
  • 1
  • 25
  • 54
3

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!

c0d3ster
  • 51
  • 3
2

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!

Brian Burns
  • 20,575
  • 8
  • 83
  • 77
Matt Ellis
  • 451
  • 3
  • 6
1

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)
  });
}
julian libor
  • 1,583
  • 1
  • 9
  • 8
1

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) }));
Brian Burns
  • 20,575
  • 8
  • 83
  • 77
0

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!!

Saddam
  • 1,174
  • 7
  • 14
0

In react setState

const [array, setArray] = useState<any>([]);
//element you want to remove
let temp = array.filter((val: number) => {
    return val !== element;
 });
setArray(temp);
Mujahidul Islam
  • 265
  • 4
  • 8
-2

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!!!

T04435
  • 12,507
  • 5
  • 54
  • 54