416

I'm reading Forms section of documentation and just tried this code to demonstrate onChange usage (JSBIN).

var React= require('react');

var ControlledForm= React.createClass({
    getInitialState: function() {
        return {
            value: "initial value"
        };
    },

    handleChange: function(event) {
        console.log(this.state.value);
        this.setState({value: event.target.value});
        console.log(this.state.value);

    },

    render: function() {
        return (
            <input type="text" value={this.state.value} onChange={this.handleChange}/>
        );
    }
});

React.render(
    <ControlledForm/>,
  document.getElementById('mount')
);

When I update the <input/> value in the browser, the second console.log inside the handleChange callback prints the same value as the first console.log, Why I can't see the result of this.setState({value: event.target.value}) in the scope of handleChange callback?

Salah Eddine Taouririt
  • 24,925
  • 20
  • 60
  • 96
  • 7
    If you're using hooks, take a look at [useState set method not reflecting change immediately](https://stackoverflow.com/q/54069253/1218980). – Emile Bergeron Nov 13 '19 at 20:54
  • React 18 has introduced [automatic batching](https://stackoverflow.com/a/71772322/3340702) – lifeisfoo Apr 06 '22 at 19:12

10 Answers10

719

From React's documentation:

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

If you want a function to be executed after the state change occurs, pass it in as a callback.

this.setState({value: event.target.value}, function () {
    console.log(this.state.value);
});
Michael Parker
  • 12,724
  • 5
  • 37
  • 58
  • Good answer. The observation that I need to do is be careful to use valueLink. It works good if you not have to format/mask the input. – Dherik Dec 18 '15 at 02:31
  • 52
    You might also want to check out `componentDidUpdate`. It will be called after the state has changed. – Keysox May 16 '16 at 21:34
  • 2
    Quick question if i may, i see once we pass the function which we need as callback to setState , i was hoping that the func would be executed first before render() is called. But i see the order is setState() -> render() -> setStates' callback() . is this normal? What if we want to control our render based on the stuff we do in callback ? _shouldComponentUpdate_? – semuzaboi Jun 02 '16 at 10:44
  • 2
    Changing state of a component will always trigger a re-render unless there is behavior in `shouldComponentUpdate` that specifies otherwise. What exactly are you trying to do in the callback you're passing to `setState` that you want to occur before the re-render? – Michael Parker Jun 02 '16 at 14:39
  • 4
    ...why? Could someone justify this? – JackHasaKeyboard Sep 01 '17 at 12:26
  • How could I add the prevState and props to a setState with callback? Adding a callback to my working setStates with prevStates makes them to stop working. – Deses Sep 06 '17 at 16:35
  • @JackHasaKeyboard What I was told was that it's done for performance reasons. The setState is actually completed when there are resources to do so. – Highspeed Aug 08 '19 at 20:08
  • What about `useState` in hooks that doesn't receive immediate change either? We cannot pass the callback. – RA. Aug 20 '19 at 02:46
  • What if the callback does not work as you said. mine does not! – Ghasem Aug 29 '19 at 06:31
  • Is this still correct answer for updated react version? I am using this and getting a warning like `Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().` – Devil's Dream Sep 20 '22 at 05:58
57

As mentioned in the React documentation, there is no guarantee of setState being fired synchronously, so your console.log may return the state prior to it updating.

Michael Parker mentions passing a callback within the setState. Another way to handle the logic after state change is via the componentDidUpdate lifecycle method, which is the method recommended in React docs.

Generally we recommend using componentDidUpdate() for such logic instead.

This is particularly useful when there may be successive setStates fired, and you would like to fire the same function after every state change. Rather than adding a callback to each setState, you could place the function inside of the componentDidUpdate, with specific logic inside if necessary.

// example
componentDidUpdate(prevProps, prevState) {
  if (this.state.value > prevState.value) {
    this.foo();  
  }
}
Yo Wakita
  • 5,322
  • 3
  • 24
  • 36
28

You could try using ES7 async/await. For instance using your example:

handleChange: async function(event) {
    console.log(this.state.value);
    await this.setState({value: event.target.value});
    console.log(this.state.value);
}
kurokiiru
  • 303
  • 3
  • 6
  • How is your answer different from the other high quality answer? – tod Oct 22 '18 at 23:30
  • 11
    The other answer is with respect to using the callback in setState(). I thought I put this here for those whom a callback use case doesn't apply. For instance, when I faced this problem myself, my use case involved a switch case on the updated state right after setting it. Therefore using async/await was preferred to using a callback. – kurokiiru Oct 24 '18 at 07:36
  • will this impact performance if I always use await always when I want to update some state and then wait for it to have updated? And if I put multiple await setStates in a chain one below another, will it render after each setState update? or after the last setState update? – MMMM Jul 07 '19 at 19:21
  • 8
    [`setState` is asynchronous but doesn't return anything](https://github.com/facebook/react/blob/b43785e151a06c91e4c8b3e6e01b615bdc37e33a/packages/react/src/ReactBaseClasses.js#L33-L67), so [`await` will work most of the time but only by chance as it's a race-condition.](https://stackoverflow.com/a/45744345/1218980) – Emile Bergeron Jul 26 '19 at 17:10
14

Watch out the react lifecycle methods!

I worked for several hours to find out that getDerivedStateFromProps will be called after every setState().

yangsibai
  • 1,637
  • 12
  • 23
10

Sometime this issue occurs with state.
In case of hooks, you should use useEffect hook, As below-

const [fruit, setFruit] = useState('');
 
setFruit('Apple');
useEffect(() => {
  console.log('Fruit', fruit);
}, [fruit])

This saved my day, Hope will help you!!!

S.Yadav
  • 4,273
  • 3
  • 37
  • 44
4

Accessing this.state after calling the setState method is not guaranteed to return the updated status due to the asynchronous nature of setState.

To guarantee an update after calling setState, there are two solutions you may pursue.

Solution 1: As mentioned in one of the above answers, put your code in the componentDidUpdate method

Solution 2: As mentioned in another of the above answers, pass your stuff as a callback

 this.setState({value: myValue}, function () {
    this.functionThatIsExecutedWhenStateIsUpdated();
});

It's important to note that these two solutions are not clearly interchangeable. The one cannot easily solve all the use-cases of the other. As a general rule, if you can, best practice says that solution 1 is preferred. But, there are use-cases where only solution 2 "more effectively" works such as the "update-my-view-and-post-my-data" use case. This use case goes like this:

After adding an item, say, "Add Schedule", I want to both add that item to a front-end list and immediately post the just-updated-list to the backend, as demonstrated in the concept below: enter image description here

If you dont do either solution, i.e. if you only say this in your code:

addToItemArray = () => { 
     this.setState{{ scheduledItemsArray: newObjectListWithMax}}   
     this.postData();
}

<button className="btn btn-secondary btn-block" onClick={this.addToItemArray}>Add Shedule</button>

... you will post the list excluding the "Delivery to Max" item, because the state wont be updated when you this.postData() (again, because its asynchronous).

If you utilise solution 1, you would make a POST after typing in every character in the Schedule Name textbox!

There are other ways aswell to cater for this use-case but solution 2 best conveys the intent when reading the code.

Given the ubiquitous nature of this use case in virtually every web app, the callback technique explained by Michael's answer is an indispensable piece of code in every developers toolkit.

Dean P
  • 1,841
  • 23
  • 23
3

async-await syntax works perfectly for something like the following...

changeStateFunction = () => {
  // Some Worker..

  this.setState((prevState) => ({
  year: funcHandleYear(),
  month: funcHandleMonth()
}));

goNextMonth = async () => {
  await this.changeStateFunction();
  const history = createBrowserHistory();
  history.push(`/calendar?year=${this.state.year}&month=${this.state.month}`);
}

goPrevMonth = async () => {
  await this.changeStateFunction();
  const history = createBrowserHistory();
  history.push(`/calendar?year=${this.state.year}&month=${this.state.month}`);
}
Ritwik
  • 41
  • 1
3

React bathces different set state calls so that it can determine what is the most optimal strategy for rerendering the website is going to be.

Imagine you have an application where you have a lot of different components. Perhaps, with one button click you are updating the state in multiple components, not just on the current one. In this case, React does not want to just completely isolate and do all those different updates independently.

React wants to figure out if it can stack all these updates together, maybe there is a more optimal way of updating these components so that it is more performant. This is what React is doing behind the scenes. As a result, set state call is asynchronous call.

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
0

Simply putting - this.setState({data: value}) is asynchronous in nature that means it moves out of the Call Stack and only comes back to the Call Stack unless it is resolved.

Please read about Event Loop to have a clear picture about Asynchronous nature in JS and why it takes time to update -

https://medium.com/front-end-weekly/javascript-event-loop-explained-4cd26af121d4

Hence -

    this.setState({data:value});
    console.log(this.state.data); // will give undefined or unupdated value

as it takes time to update. To achieve the above process -

    this.setState({data:value},function () {
     console.log(this.state.data);
    });
Vishal Bisht
  • 139
  • 2
  • 1
0

The reason why you are seeing the same value in both console logs is because setState() is an asynchronous function. Therefore, the console log after setState() is executed before the state is actually updated.

If you want to see the updated value of this.state.value in the handleChange() callback, you should use the second argument of setState(), which is a callback function that is executed once the state is updated. You can modify the handleChange() function like this:

handleChange: function(event) {
  console.log(this.state.value);
  this.setState({value: event.target.value}, function() {
    console.log(this.state.value);
  });
},

This will log the updated value of this.state.value in the callback function of setState().