Sri
Sri

Reputation: 2328

Why does setting useState inside a loop perform differently?

Can someone explain the following code snippet to me please? Why does handleClick() not work properly (only 1 or 2 of the data properties of newData are set) whereas handleClick2() works properly.

I am guessing the problem is due to the loop (since that is the only difference), but why does a loop cause this effect?

sandbox link: https://codesandbox.io/s/restless-surf-u9i5s?file=/src/App.js:0-969

import React, { useState, useEffect } from "react";
import "./styles.css";


const App = () => {
  const [newData, setNewData] = useState({
    data1: '',
    data2: '',
    data3: '',
    data4: ''
  })

  const handleClick = () => {
    let fields = ['data1', 'data2', 'data3', 'data4']
    let field
    for (field of fields) {
        setNewData(oldState => ({...oldState, [field]: 'test'}))
    }
  }

  const handleClick2 = () => {
    setNewData(oldState => ({...oldState, data1: 'test'}))
    setNewData(oldState => ({...oldState, data2: 'test'}))
    setNewData(oldState => ({...oldState, data3: 'test'}))
    setNewData(oldState => ({...oldState, data4: 'test'}))
  }

  useEffect(() => {
    console.log('State: ' + JSON.stringify(newData))
  }, [newData])

  return (
    <div className="App">
      <button onClick={handleClick}>Click</button>
      <button onClick={handleClick2}>Click2</button>
    </div>
  );
}

export default App

Upvotes: 0

Views: 28

Answers (1)

Tony Nguyen
Tony Nguyen

Reputation: 3488

The reason is:

setNewData is async function. When you call setNewData somehow React will push its callback function in event loop then call it back in main thread when the main thread empty. When setNewData callback function is call in main field is test4 because for (field of fields) sync function. ==> only setNewData(oldState => ({...oldState, data4: 'test'})) is call 4 times.

You can log the field inside of setNewData callback function and see the value of it.

for (field of fields) {
  console.log(field)
  setNewData(oldState => {
    console.log(field)
    return { ...oldState, [field]: "test" }
  })
}

Codesandbox for it

Upvotes: 1

Related Questions