609

I want to add an element to the end of a state array, is this the correct way to do it?

this.state.arrayvar.push(newelement);
this.setState({ arrayvar:this.state.arrayvar });

I'm concerned that modifying the array in-place with push might cause trouble - is it safe?

The alternative of making a copy of the array, and setStateing that seems wasteful.

Or Assayag
  • 5,662
  • 13
  • 57
  • 93
fadedbee
  • 42,671
  • 44
  • 178
  • 308

19 Answers19

979

The React docs says:

Treat this.state as if it were immutable.

Your push will mutate the state directly and that could potentially lead to error prone code, even if you are "resetting" the state again afterwards. For example, it could lead to that some lifecycle methods like componentDidUpdate won’t trigger.

The recommended approach in later React versions is to use an updater function when modifying states to prevent race conditions:

this.setState(prevState => ({
  arrayvar: [...prevState.arrayvar, newelement]
}))

The memory "waste" is not an issue compared to the errors you might face using non-standard state modifications.

Alternative syntax for earlier React versions

You can use concat to get a clean syntax since it returns a new array:

this.setState({ 
  arrayvar: this.state.arrayvar.concat([newelement])
})

In ES6 you can use the Spread Operator:

this.setState({
  arrayvar: [...this.state.arrayvar, newelement]
})
isherwood
  • 58,414
  • 16
  • 114
  • 157
David Hellsing
  • 106,495
  • 44
  • 176
  • 212
  • 10
    Can you provide an example of when a race condition would occur? – soundly_typed Feb 11 '15 at 02:12
  • Note that this answer only applies in case you want **only** to add/remove items. If you wanted to update elements in the array, you could get in trouble if it held objects. Consider this code: `var a = [1,2,3, {a:1}]; var b = a.slice(); b[3].a = 2; console.log(a[3].a);` – VitalyB May 11 '15 at 21:57
  • @VitalyB In that case, use underscore/jquery extend or some immutable library (like react immutability helper) instead of concat. – David Hellsing May 12 '15 at 08:46
  • Even though the original reference gets changed, the setState call will call the render pass again, and unless you have something like PureRenderMixin turned on, you'll get your new render just fine. Is there anything else inherently wrong with `this.setState({arr: this.state.arr.push(item)})`? – Qiming Jun 30 '15 at 06:30
  • 3
    @Qiming `push` returns the new array length so that won’t work. Also, `setState` is async and React can queue several state changes into a single render pass. – David Hellsing Jul 01 '15 at 09:34
  • @ChristopherCamps I think that's another discussion. You can treat the second setState as callback from the first setState. Example in ES6 syntax: `this.SetState({ arr: this.state.arr.push(item)}, () => { this.setState({ this.state.arr.push(item2)}) })` – rhzs Mar 02 '16 at 18:00
  • 2
    @mindeavor say you have an animationFrame that looks for parameters in this.state, and another method that changes some other parameters on state change. There could be some frames where the state has changed but not reflected in the method that listens for the change, because setState is async. – David Hellsing Dec 21 '16 at 13:06
  • See @NealeU's answer below for the correct way to handle the race condition that ChristopherCamps mentions. That seems like a more robust way to handle lists in general. – vish Jan 08 '17 at 03:37
  • 1
    @ChristopherCamps This answer does not encourage calling `setState` twice, it shows two similar examples of setting state array without mutating it directly. – David Hellsing Feb 08 '17 at 12:34
  • 1
    @David I would advise updating your answer to include @NealeU's version also. Here's how I would do it now to avoid mutating anything AND be safe for repeat calls: `this.setState(state => ({ ...state, arrayvar: state.arrayvar.concat([newelement]) }));` – Christopher Camps Feb 09 '17 at 17:33
  • 3
    An easy way to treat a state array as immutable these days is: `let list = Array.from(this.state.list); list.push('woo'); this.setState({list});` Modify to your style preferences of course. – basicdays Mar 03 '17 at 16:57
  • Now with objects! `let obj = Object.assign({}, this.state.obj); obj.woo = 'wow'; this.setState({obj});` – basicdays Mar 03 '17 at 16:59
  • 1
    This answer really does need updating to put the callback approach first, otherwise it's doing people a disservice. Perhaps I'll try another edit having had my attempt to underline the risks that @ChristopherCamps pointed out, reverted by the author. – NealeU Feb 28 '18 at 14:19
  • @David Where did the prevState come from and can you explain how the arrow function operates in your example above? – AKJ Nov 09 '18 at 10:04
  • what about not using push but instead inserting in the middle using splice, we know splice affects the array itself. do we need to copy it using slice(0) and then splice it and assign it? does this seem reasonable resource optimization? why not modify it directly then setting any random state to update it, won't it make the same effect? – CME64 May 13 '19 at 15:18
  • How can i prevent the duplicate after contacting two arrays when setState? i use this way `this.state.arrayvar.concat([newelement])` – Oliver D Apr 08 '20 at 22:25
167

Easiest, if you are using ES6.

initialArray = [1, 2, 3];

newArray = [ ...initialArray, 4 ]; // --> [1,2,3,4]

New array will be [1,2,3,4]

to update your state in React

this.setState({
  arrayvar:[...this.state.arrayvar, newelement]
});

Learn more about array destructuring

David Hellsing
  • 106,495
  • 44
  • 176
  • 212
StateLess
  • 5,344
  • 3
  • 20
  • 29
  • 1
    Append or prepend is quite straightforward. What about search and replace? For example array of objects. I need to update one object searching by id? – Sisir Jul 07 '17 at 12:11
  • 2
    Your questions doesn't concern the OP question directly – StateLess Jul 10 '17 at 12:14
  • When is `prevState` needed in `setState()`? ...or is `prevState` dated? – Chance Smith Aug 17 '18 at 11:45
  • 1
    @ChanceSmith: it is needed in *StateLess* answer too. Do not depend in state update on the state itself. Official doc: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous – arcol Dec 26 '18 at 11:25
  • 2
    @Sisir and anyone wondering [how to update an object within an array in the state](https://stackoverflow.com/q/28121272/1218980). – Emile Bergeron Aug 06 '19 at 19:27
  • 1
    @RayCoder log and check the value of arrayvar, looks like it is not array. – StateLess Sep 11 '19 at 09:04
  • this.state = { suggestions: [{ id: "Thailand", text: "Thailand" }], }; this.setState( {suggestions: [...this.state.suggestions,[{ id: "India", text: "India" }]] } ); I tried this and using concat one . but both are useless... – user14232549 Apr 01 '21 at 04:19
72

The simplest way with ES6:

this.setState(prevState => ({
    array: [...prevState.array, newElement]
}))
David Hellsing
  • 106,495
  • 44
  • 176
  • 212
Ridd
  • 10,701
  • 3
  • 19
  • 20
  • Sorry, in my case i want to push an array into array. ```tableData = [['test','test']]``` After pushed my new array ```tableData = [['test','test'],['new','new']]```. how to push this @David and @Ridd – Johncy Sep 21 '18 at 12:08
  • @Johncy If you would like to get `[['test','test'],['new','new']]` try: `this.setState({ tableData: [...this.state.tableData, ['new', 'new']]` – Ridd Sep 23 '18 at 17:52
  • `this.setState({ tableData: [...this.state.tableData ,[item.student_name,item.homework_status_name,item.comments===null?'-':item.comments] ] });` It inserts the new array two times `this.state.tableData.push([item.student_name,item.homework_status_name,item.comments===null?'-':item.comments]);` It achieves the desired thing i want. but its not the correct way i think. – Johncy Sep 24 '18 at 06:28
42

Currently so many people facing problem to update the useState hook state. I use this approach to update it safely and wanted to share it here.

This is my state

const [state, setState] = useState([])

Suppose I have a object name obj1 and I want it to append in my state. I will suggest to do it like this

setState(prevState => [...prevState, obj1])

This will safely insert the object at the end and also keep the state consistency

moshfiqrony
  • 4,303
  • 2
  • 20
  • 29
  • 3
    THIS is the correct way to update a state when its new value depends on the previous one. Docs: *If the next state depends on the current state, we recommend using the updater function form:* `setState((state) => ...`, see https://en.reactjs.org/docs/react-component.html#setstate -- State updates do NOT happen immediately, so when using the original state variable, some updates may get overwritten by new ones. – Marcin Wojnarski Dec 03 '21 at 14:45
  • @MarcinWojnarski Except that it doesn't work when stuff ought to be appended concurrently… – Boiethios Dec 24 '22 at 17:10
33

React may batch updates, and therefore the correct approach is to provide setState with a function that performs the update.

For the React update addon, the following will reliably work:

this.setState( state => update(state, {array: {$push: [4]}}) );

or for concat():

this.setState( state => ({
    array: state.array.concat([4])
}));

The following shows what https://jsbin.com/mofekakuqi/7/edit?js,output as an example of what happens if you get it wrong.

The setTimeout() invocation correctly adds three items because React will not batch updates within a setTimeout callback (see https://groups.google.com/d/msg/reactjs/G6pljvpTGX0/0ihYw2zK9dEJ).

The buggy onClick will only add "Third", but the fixed one, will add F, S and T as expected.

class List extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      array: []
    }

    setTimeout(this.addSome, 500);
  }

  addSome = () => {
      this.setState(
        update(this.state, {array: {$push: ["First"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Second"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Third"]}}));
    };

  addSomeFixed = () => {
      this.setState( state => 
        update(state, {array: {$push: ["F"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["S"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["T"]}}));
    };



  render() {

    const list = this.state.array.map((item, i) => {
      return <li key={i}>{item}</li>
    });
       console.log(this.state);

    return (
      <div className='list'>
        <button onClick={this.addSome}>add three</button>
        <button onClick={this.addSomeFixed}>add three (fixed)</button>
        <ul>
        {list}
        </ul>
      </div>
    );
  }
};


ReactDOM.render(<List />, document.getElementById('app'));
NealeU
  • 1,264
  • 11
  • 23
  • Is there really a case where it happens ? If we simply do ```this.setState( update(this.state, {array: {$push: ["First", "Second", "Third"]}}) )``` – Albizia Jul 24 '19 at 20:25
  • 1
    @Albizia I think you should find a colleague and discuss it with them. There is no batching issue if you are making only one setState call. The point is to show that React batches updates, so yes... there is really a case, which is what you can find in the JSBin version of the above code. Almost all of the answers in this thread fail to address this, so there will be a lot of code out there that sometimes goes wrong – NealeU Aug 04 '19 at 12:37
  • `state.array = state.array.concat([4])` this mutates the previous state object. – Emile Bergeron Aug 06 '19 at 19:32
  • @EmileBergeron The difference is that modified `state` (a copy of this.state) as provided to the callback. It does not modified `this.state` – NealeU Aug 09 '19 at 15:17
  • `state` *is* the previous state object. – Emile Bergeron Aug 09 '19 at 15:31
  • 1
    @EmileBergeron Thank you for your persistence. I eventually looked back and saw what my brain was refusing to see, and checked the docs, so I'll edit. – NealeU Oct 31 '19 at 17:06
  • 1
    Good! It's really easy to get wrong as immutability in JS is non-obvious (even more so when dealing with the API of a library). – Emile Bergeron Oct 31 '19 at 17:19
31

If you are using functional components in React

const [cars, setCars] = useState([{
  name: 'Audi',
  type: 'sedan'
}, {
  name: 'BMW',
  type: 'sedan'
}])

...

const newCar = {
  name: 'Benz',
  type: 'sedan'
}

const updatedCarsArray = [...cars, newCar];

setCars(updatedCarsArray);
coderpc
  • 4,119
  • 6
  • 51
  • 93
21

As @nilgun mentioned in the comment, you can use the react immutability helpers. I've found this to be super useful.

From the docs:

Simple push

var initialArray = [1, 2, 3];
var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]

initialArray is still [1, 2, 3].

Clarkie
  • 7,490
  • 9
  • 39
  • 53
  • 8
    The React immutability helpers are described as deprecated in the documentation. https://github.com/kolodny/immutability-helper should now be used instead. – Periata Breatta Jan 20 '17 at 07:52
  • This answer and this comment is the real thing, and I appreciate taking time to write it in such a concise way - both of you. `immutability-helper` expresses this concern and has lot of thoughts and code in this direction. – Priya Ranjan Singh Feb 24 '22 at 04:45
8

If you are using functional component please use this as below.

const [chatHistory, setChatHistory] = useState([]); // define the state

const chatHistoryList = [...chatHistory, {'from':'me', 'message':e.target.value}]; // new array need to update
setChatHistory(chatHistoryList); // update the state
1
this.setState(preState=>({arrayvar:[...prevState.arrayvar,newelement]}))

this will work to solve this problem.

kyun
  • 9,710
  • 9
  • 31
  • 66
raja saad
  • 19
  • 1
0

For added new element into the array, push() should be the answer.

For remove element and update state of array, below code works for me. splice(index, 1) can not work.

const [arrayState, setArrayState] = React.useState<any[]>([]);
...

// index is the index for the element you want to remove
const newArrayState = arrayState.filter((value, theIndex) => {return index !== theIndex});
setArrayState(newArrayState);
U13-Forward
  • 69,221
  • 14
  • 89
  • 114
Jagger
  • 2,352
  • 2
  • 13
  • 12
0

Here's a 2020, Reactjs Hook example that I thought could help others. I am using it to add new rows to a Reactjs table. Let me know if I could improve on something.

Adding a new element to a functional state component:

Define the state data:

    const [data, setData] = useState([
        { id: 1, name: 'John', age: 16 },
        { id: 2, name: 'Jane', age: 22 },
        { id: 3, name: 'Josh', age: 21 }
    ]);

Have a button trigger a function to add a new element

<Button
    // pass the current state data to the handleAdd function so we can append to it.
    onClick={() => handleAdd(data)}>
    Add a row
</Button>
function handleAdd(currentData) {

        // return last data array element
        let lastDataObject = currentTableData[currentTableData.length - 1]

        // assign last elements ID to a variable.
        let lastID = Object.values(lastDataObject)[0] 

        // build a new element with a new ID based off the last element in the array
        let newDataElement = {
            id: lastID + 1,
            name: 'Jill',
            age: 55,
        }

        // build a new state object 
        const newStateData = [...currentData, newDataElement ]

        // update the state
        setData(newStateData);

        // print newly updated state
        for (const element of newStateData) {
            console.log('New Data: ' + Object.values(element).join(', '))
        }

}
Ian Smith
  • 879
  • 1
  • 12
  • 23
  • What if instead of adding, I wanted to remove an element from the array? – Ken Feb 07 '21 at 03:43
  • @Ken what kind of array are you working with? Your array object should have a built in remove function built in. You'd trigger the removal and then update the state. – Ian Smith Feb 07 '21 at 10:07
0

What I do is update a value outside the state and do a forceupdate(), the less stuff managed by react the better since you have more control over what is updated. Also creating a new array for every update may be too expensive if the updates are fast

meiser
  • 31
  • 3
  • Your answer could be improved by including a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Besworks Apr 05 '22 at 19:34
-1

I am trying to push value in an array state and set value like this and define state array and push value by map function.

 this.state = {
        createJob: [],
        totalAmount:Number=0
    }


 your_API_JSON_Array.map((_) => {
                this.setState({totalAmount:this.state.totalAmount += _.your_API_JSON.price})
                this.state.createJob.push({ id: _._id, price: _.your_API_JSON.price })
                return this.setState({createJob: this.state.createJob})
            })
Rajnikant Lodhi
  • 530
  • 4
  • 13
-1

I was having a similar issue when I wanted to modify the array state while retaining the position of the element in the array

This is a function to toggle between like and unlike:

    const liker = (index) =>
        setData((prevState) => {
            prevState[index].like = !prevState[index].like;
            return [...prevState];
        });

as we can say the function takes the index of the element in the array state, and we go ahead and modify the old state and rebuild the state tree

Andrzej Sydor
  • 1,373
  • 4
  • 13
  • 28
L Andy
  • 1
  • 1
-2

This worked for me to add an array within an array

this.setState(prevState => ({
    component: prevState.component.concat(new Array(['new', 'new']))
}));
MadKad
  • 1
-2
//get the value you want to add
const valor1 = event.target.elements.valor1.value;

//add in object 
        const todo = {
            valor1,
        }

//now you just push the new value into the state

//prevlista is the value of the old array before updating, it takes the old array value makes a copy and adds a new value

setValor(prevLista =>{
return prevLista.concat(todo) })
     
-2
this.setState((prevState) => {   
    const newArray = [...prevState.array];   
    newArray[index] = updatedValue;   
    return { array: newArray };   
});

this is how we update array in reacjs

CR241
  • 2,293
  • 1
  • 12
  • 30
  • Please avoid code only answer, and add some explanation. Especially when answering to old questions, it is important to explain why your answer is different, and even better than existing answers. – chrslg Feb 06 '23 at 05:00
-3
//------------------code is return in typescript 

const updateMyData1 = (rowIndex:any, columnId:any, value:any) => {

    setItems(old => old.map((row, index) => {
        if (index === rowIndex) {
        return Object.assign(Object.assign({}, old[rowIndex]), { [columnId]: value });
    }
    return row;
}));
Mesut Akcan
  • 899
  • 7
  • 19
-10

This code work for me:

fetch('http://localhost:8080')
  .then(response => response.json())
  .then(json => {
    this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
  })
  • 2
    still, you mutate state directly – Asbar Ali Jan 30 '19 at 08:51
  • 2
    And fails to update the component's state correctly since [`.push` returns a number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push#Return_value), not an array. – Emile Bergeron Aug 22 '19 at 19:15