97

Let's say I have a list of plain objects in my this.state.list that I can then use to render a list of children. What then is the right way to insert object into this.state.list?

Below is the only way I think it will work because you can not mutate the this.state directly as mentioned in the doc.

this._list.push(newObject):
this.setState({list: this._list});

This seems ugly to me. Is there a better way?

Khoi
  • 4,502
  • 6
  • 29
  • 31
  • possible duplicate of [Correct modification of state arrays in ReactJS](http://stackoverflow.com/questions/26253351/correct-modification-of-state-arrays-in-reactjs), requesting merge – Brigand Oct 22 '14 at 12:40
  • All those answers are about adding an element to an array. But what about removing an element from it? Or simply to completely reset the array to a new one? – Augustin Riedinger Feb 19 '16 at 17:16

4 Answers4

164

concat returns a new array, so you can do

this.setState({list: this.state.list.concat([newObject])});

another alternative is React's immutability helper

  var newState = React.addons.update(this.state, {
      list : {
        $push : [newObject]
      }
  });

  this.setState(newState);
Brigand
  • 84,529
  • 20
  • 165
  • 173
Heap
  • 2,346
  • 1
  • 19
  • 12
  • 6
    `concat` takes an array, so you'll want `this.state.list.concat([newObject])`. – Sophie Alpert May 31 '14 at 07:57
  • @BenAlpert works for me in Chrome, are you saying this is non-standard behavior? – Heap May 31 '14 at 08:38
  • 6
    @Heap Well, if you pass a non-array to `concat` it will wrap it in an array, but it's better to add the array yourself to be explicit: if you have `[[1,2], [3,4]]` and want to add `[5,6]` as the next element, you need to do `.concat([[5,6]])` else you'll end up with `[[1,2], [3,4], 5, 6]`. – Sophie Alpert May 31 '14 at 21:00
  • 2
    As much as I appreciate the idea behind immutability helpers (and I may end up using it anyway), `Array.concat` sure beats adding another library. – Shawn Erquhart Jan 02 '16 at 18:25
  • Using concat for load more button I got what I need in my react app, but in the console I have some errors "Warning: flattenChildren(...): Encountered two children with the same key," – Hamza MHIRA Oct 18 '16 at 13:47
  • 1
    Calling this.setState with a value derived from this.state will have you fall foul of update batching issues. See http://stackoverflow.com/a/41445812/1998186 which links to a jsfiddle showing the problem you'll get. – NealeU Jan 03 '17 at 14:59
41

setState() can be called with a function as a parameter:

this.setState((state) => ({ list: state.list.concat(newObj) }))

or in ES5:

this.setState(function(state) {
  return {
   list: state.list.concat(newObj)
  }
})
Thibaut
  • 607
  • 6
  • 7
Nyalab
  • 527
  • 4
  • 4
  • I wonder if react only renders the newly added element or the whole array ? – Arian Aug 29 '15 at 23:50
  • 2
    @Arian: React will exectue the required DOM mutation instructions necessary to render only the newly added elements. Given of course that you're newly added elements don't change how the previous elements rendered. – Jose Browne Oct 05 '15 at 06:17
  • 1
    This is a fantastic answer and feels so much cleaner than concatting array just to perform a push, although thats opinion I guess. – Matt Styles Oct 13 '15 at 08:46
  • 2
    @Nyalab shouldn't you be wrapping the function body in parenthesis? As it stands right now in your example, the braces after the fat arrow would start a block, not an object. Something like this: `this.setState((state) => ({ list: state.list.push(newObj) }))` – kumarharsh Nov 02 '15 at 07:02
  • I believe this is still mutating `this.state`. The docs specifically mention that this reference to state is provided so that it's value can be consulted, and their example does not mutate the value. – Shawn Erquhart Jan 02 '16 at 18:17
  • @kumar_harsh I was inclined to agree with the idea that it should be wrapped in parens, but that fails with the error: `Uncaught TypeError: this.props.friends.map is not a function`. It works great fine without them. – Henry Marshall Jan 20 '16 at 16:59
  • 3
    How is this code working? the push method will return a `NUMBER`, which will be set on the `list` variable – kumarharsh Jan 21 '16 at 06:42
  • @kumar_harsh I think both of the problems you mentioned are good points - I just edited Nyalab's answer to fix them, but free free to tweak things on your own if you think I erred. – JKillian Feb 19 '16 at 16:59
  • 1
    You could also use a splat: `this.setState((state) => ({ list: [...state.list, newObj] }))` – amoebe Oct 26 '16 at 20:26
20

Update 2016

With ES6 you can use:

this.setState({ list: [...this.state.list, ...newObject] });
Ashish Chaudhary
  • 798
  • 5
  • 12
7

From the react docs (https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous):

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

So you should do this instead:

this.setState((prevState) => ({
  contacts: prevState.contacts.concat([contact])
}));
jcgzzc
  • 121
  • 1
  • 6