37

So here is my state:

this.state = {
  ids: ['A', 'E', 'C']
};

How would I go about modifying the state so that 'E' at index 1 is changed to 'B'? Like for example:

this.setState({
  ids[1]: 'B'
});

How would this be done?

pizzae
  • 2,815
  • 6
  • 26
  • 45
  • 2
    Today Array.prototype.map() is your best friend, take a look at [here](https://www.robinwieruch.de/react-state-array-add-update-remove) – Cris69 Sep 12 '19 at 13:52

6 Answers6

78

My suggestion is to get used to use immutable operations, so you don't modify internal state object.

As pointed in the react docs:

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

In this case, you can [1] use slice() to get a new copy of the Array, [2] manipulate the copy, and, then, [3] setState with the new Array. It's a good practice.

Something like that:

const newIds = this.state.ids.slice() //copy the array
newIds[1] = 'B' //execute the manipulations
this.setState({ids: newIds}) //set the new state
Si8
  • 9,141
  • 22
  • 109
  • 221
mrlew
  • 7,078
  • 3
  • 25
  • 28
  • i think meaning of that line is this: `this.state.a='a'`, don't change any state variable value directly. **treat this.state as if it were immutable**, Always use `setState` to change the state values. correct me if i am wrong ?? – Mayank Shukla Feb 04 '17 at 06:52
  • Arrays are Objects in JS, so they are passed by *"reference"* (no problem if you were setting a state key with a new string). But modifying the same array/object from `this.state` will change the same internal object. – mrlew Feb 04 '17 at 06:55
  • 1
    Thank you for this. I spent way too much time wondering why React didn't see a state change from just updating state with a modified copy of an array. Not until I made that copy using `slice()` did it see a state change. – Dave Sep 13 '19 at 19:04
  • 3
    It took me an hour of searching to find a straight to the point simple explanation of how to do this. Copy the array and edit the copy and then setState from the copy. Simple. Thanks for this! –  Jan 13 '20 at 19:35
36

Case 1: If you know the index then you can write it like this:

let ids = [...this.state.ids];     // create the copy of state array
ids[index] = 'k';                  //new value
this.setState({ ids });            //update the value

Case 2: If you don't know the index then first use array.findIndex or any other loop to get the index of item you want to update, after that update the value and use setState.

Like this:

let ids = [...this.state.ids];  
let index = ids.findIndex(el => /* condition */);
ids[index] = 'k';                  
this.setState({ ids });            
Mayank Shukla
  • 100,735
  • 18
  • 158
  • 142
6

Here is another solution to change a specific index of array in a setState:

this.setState({
  ...array,
  Object.assign([...array], { [id]: yourNewObjectOrValue })
})
Nvan
  • 1,126
  • 16
  • 23
2

Building on what @mayank-shukla wrote (case 2: knowing the index of the item to replace), this could also be written with Array.splice:

const replacement = 'B';
let copy = [...this.state.ids]
copy.splice(index, 1, replacement)

this.setState({ 
   ids: copy,
})

REPL Example

Two things to note, here:

  1. Array.splice is mutative; It will change the array it's operating on, but this is a shallow copy of the array because of the spread operator. More on that below.
  2. You can't directly assign the result of a splice, as the return value of Array.splice is actually the deleted element(s). AKA: Do not assign the result of your slice to the variable you intend to assign to IDs in setState, or you will end up with only the deleted value(s).

To follow up on shallow vs deep copies from item 1, please note that if you are replacing object references (vs string literals in the question), you will need to use something like lodash's cloneDeep.

There are a handful of other ways around this, though.

You can also read more about shallow vs deep on SO itself.

zedd45
  • 2,101
  • 1
  • 31
  • 34
  • "You can't directly assign the result of a splice, as the return value of Array.splice is actually the deleted element(s). AKA: Do not assign the result of your slice to the variable you intend to assign to IDs in setState, or you will end up with only the deleted value(s)." **Doesn't your example do just that?** – Aaron Aug 10 '18 at 21:55
  • 1
    @Aaron on the post, yes. In the REPL, no. I will update the post itself. – zedd45 Aug 16 '18 at 20:27
1
this.setState({
  ids: [ids[1]='B',...ids].slice(1)
});

The above code will create two array items of value 'B' ,
one at the begining and one at the specified position. simply use slice operator to remove the first array element.

Another Solution is to use splice operator to do it directly

this.setState({
  ids: [ids.splice(1,1,'B')]
})
Hussam Khatib
  • 600
  • 6
  • 17
1

I realize this question is old but today there's a better answer.

immer can be used to support a more sensible way to change immutable objects, with a familiar API. By passing a function to this.setState, you can use it to transform your state.

this.setState(state => produce(state, state => {
  state.ids[1] = 'b'
}));

Or, if you're more comfortable with currying, produce(fn) is a helpful shortcut to create a function that takes an argument and forwards it into your transformation:

this.setState(produce(state => {
  state.ids[1] = 'b'
}));

I've come to find that when deep state changes are involved Immer is great, but be warned that it doesn't come for free.

Fábio Santos
  • 3,899
  • 1
  • 26
  • 31