setState doesn't update the state immediately [duplicate]
Asked Answered
S

15

171

I would like to ask why my state is not changing when I do an onClick event. I've search a while ago that I need to bind the onClick function in constructor but still the state is not updating.

Here's my code:

import React from 'react';
import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import BoardAddModal from 'components/board/BoardAddModal.jsx';    
import style from 'styles/boarditem.css';

class BoardAdd extends React.Component {
    constructor(props) {
        super(props);    
        this.state = {
            boardAddModalShow: false
        };    
        this.openAddBoardModal = this.openAddBoardModal.bind(this);
    }

    openAddBoardModal() {
        this.setState({ boardAddModalShow: true }); // set boardAddModalShow to true

        /* After setting a new state it still returns a false value */
        console.log(this.state.boardAddModalShow);   
    }

    render() {    
        return (
            <Col lg={3}>
                <a href="javascript:;" 
                   className={style.boardItemAdd} 
                   onClick={this.openAddBoardModal}>
                    <div className={[style.boardItemContainer,
                                     style.boardItemGray].join(' ')}>
                        Create New Board
                    </div>
                </a>
            </Col>
        );
    }
}

export default BoardAdd
Sludge answered 22/12, 2016 at 8:3 Comment(2)
The answer you've accepted on this question makes no sense. setState doesn't return a promise. If it worked, it only worked because await introduces one async "tick" into the function, and it happened that the state update got processed during that tick. It's not guaranteed. As this answer says, you need to use the completion callback (if you really need to do something after the state is updated, which is unusual; normally, you just want to re-render, which hapens automatically).Yester
It would be good if you un-accepted the currently accepteded answer or accepted a correct one, because this could then be used as a duplicate for many other questions. Having an incorrect answer at the top is misleading.Ibert
H
2

This callback is really messy. Just use async await instead:

async openAddBoardModal(){
    await this.setState({ boardAddModalShow: true });
    console.log(this.state.boardAddModalShow);
}
Harley answered 7/9, 2018 at 15:19 Comment(8)
That makes no sense. React's setState doesn't return a promise.Yester
@T.J.Crowder is right. setState doesn't return a promise, so it should NOT be awaited. That said, I think I can see why this is working for some people, because await puts the inner workings of setState on the call stack ahead of the rest of the function, so it gets processed first, and thus seems like the state has been set. If setState had or implements any new asynchronous calls, this answer would fail.To implement this function properly, you can use this: await new Promise(resolve => this.setState({ boardAddModalShow: true }, () => resolve()))Blinders
How the hell async this.setState({}) solved my issue? We had working code, some more development has been done but nothing changed that part of the app. Only one of two objects in setState was getting updated, making it async returned functionality back and now both objects are correctly updating. I have freaking no idea what the hell was wrong.Geezer
I found this question, but this solution didn't work for me, I didn't solve my problem yet but I found a good read here: ozmoroz.com/2018/11/why-my-setstate-doesnt-workCripps
@OndřejŠevčík most likely a race-condition makes it looks like it works, but it'll possibly break again someday.Unpolitic
Calling await on a non-promise is the equivalent of calling this.setState(..); await Promise.resolve(); which has the side effect of queueing all subsequent statements for immediate execution on the microtask queue. You could push out execution further by using the traditional event loop with an await new Promise(rs => setTimeout(rs, 0)) but even, still, this is a hack, at best.Autocatalysis
ho to do that in functinal component?Cupel
@Cupel you need to use HooksHarley
G
217

Your state needs some time to mutate, and since console.log(this.state.boardAddModalShow) executes before the state mutates, you get the previous value as output. So you need to write the console in the callback to the setState function

openAddBoardModal() {
  this.setState({ boardAddModalShow: true }, function () {
    console.log(this.state.boardAddModalShow);
  });
}

setState is asynchronous. It means you can’t call it on one line and assume the state has changed on the next.

According to React docs

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.

Why would they make setState async

This is because setState alters the state and causes rerendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.

Thus the setState calls are asynchronous as well as batched for better UI experience and performance.

Gratifying answered 22/12, 2016 at 8:6 Comment(9)
Until now it is an great idea, BUT what if we can't use console.log because u're using eslint rules?Hargrave
@maudev, eslint rules stop you to have console.logs so that they don't end up in production, but the above example is purely for debugging. and console.log and be replaced with an action which takes into account the updated stateGratifying
What if the callback does not work as you said? mine does not!Aeneous
@ShubhamKhatri this worked great for me, however I then tried to pass the state value as a prop into a child component, and when I console.log the prop, I get the same value as the original state.Hubie
This answer is correct, but I think it's worth pointing out that the value you set will be available in the next render. That's when you'll most likely be using the value for something useful rather than in the setState callback as shown here, which is mostly for debugging purposes.Archaeopteryx
hi there, how can I use a callback function if I'm setting the state like this setShowModal(1);Endodermis
@VaibhavSidapara Check this post. Let me know if it helpsGratifying
@ShubhamKhatri thanks, I solved it yesterday. I used the same useEffect hook.Endodermis
React is very smart to handle complex jobs and very stupid to handle simple tasks. this feature alongside running components twice in dev environment are serial killers.Hillary
O
60

Fortunately setState() takes a callback. And this is where we get updated state.

Consider this example.

this.setState({ name: "myname" }, () => {                              
        //callback
        console.log(this.state.name) // myname
      });

So When callback fires, this.state is the updated state.
You can get mutated/updated data in callback.

Ostrich answered 9/7, 2018 at 11:16 Comment(4)
You can also use this callback to pass any function, initMap(), for instanceInterrogate
Way to go! Finally solved my problem. Thanks a lot.Amusing
this gives a warning in ReactJS functional componentDemagogy
don't depend on state and it will update the state and you will get update value through variable. let categories = [...selectedCategory, value] setSelectedCategory(categories); setInputValue(categories) using this piece of code you will get update value becuase you are not dependent on the state.Dixson
A
21

For anyone trying to do this with hooks, you need useEffect.

function App() {
  const [x, setX] = useState(5)
  const [y, setY] = useState(15) 

  console.log("Element is rendered:", x, y)

  // setting y does not trigger the effect
  // the second argument is an array of dependencies
  useEffect(() => console.log("re-render because x changed:", x), [x])

  function handleXClick() {
    console.log("x before setting:", x)
    setX(10)
    console.log("x in *line* after setting:", x)
  }

  return <>
    <div> x is {x}. </div>
    <button onClick={handleXClick}> set x to 10</button>
    <div> y is {y}. </div>
    <button onClick={() => setY(20)}> set y to 20</button>
  </>
}

Output:

Element is rendered: 5 15
re-render because x changed: 5
(press x button)
x before setting: 5
x in *line* after setting: 5
Element is rendered: 10 15
re-render because x changed: 10
(press y button)
Element is rendered: 10 20

Live version

Advise answered 21/7, 2020 at 20:20 Comment(0)
H
16

Since setSatate is a asynchronous function so you need to console the state as a callback like this.

openAddBoardModal(){
    this.setState({ boardAddModalShow: true }, () => {
        console.log(this.state.boardAddModalShow)
    });
}
Honebein answered 20/7, 2018 at 13:20 Comment(0)
A
8

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.

setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.

The first argument is an updater function with the signature:

(state, props) => stateChange

state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props. For instance, suppose we wanted to increment a value in state by props.step:

this.setState((state, props) => {
    return {counter: state.counter + props.step};
});
Abstract answered 26/11, 2018 at 18:9 Comment(0)
I
5

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

Check this for more information.

In your case you have sent a request to update the state. It takes time for React to respond. If you try to immediately console.log the state, you will get the old value.

Indira answered 13/12, 2020 at 9:8 Comment(0)
M
2

If you want to track the state is updating or not then the another way of doing the same thing is

_stateUpdated(){
  console.log(this.state. boardAddModalShow);
}

openAddBoardModal(){
  this.setState(
    {boardAddModalShow: true}, 
    this._stateUpdated.bind(this)
  );
}

This way you can call the method "_stateUpdated" every time you try to update the state for debugging.

Mileage answered 28/7, 2018 at 21:20 Comment(0)
H
2

This callback is really messy. Just use async await instead:

async openAddBoardModal(){
    await this.setState({ boardAddModalShow: true });
    console.log(this.state.boardAddModalShow);
}
Harley answered 7/9, 2018 at 15:19 Comment(8)
That makes no sense. React's setState doesn't return a promise.Yester
@T.J.Crowder is right. setState doesn't return a promise, so it should NOT be awaited. That said, I think I can see why this is working for some people, because await puts the inner workings of setState on the call stack ahead of the rest of the function, so it gets processed first, and thus seems like the state has been set. If setState had or implements any new asynchronous calls, this answer would fail.To implement this function properly, you can use this: await new Promise(resolve => this.setState({ boardAddModalShow: true }, () => resolve()))Blinders
How the hell async this.setState({}) solved my issue? We had working code, some more development has been done but nothing changed that part of the app. Only one of two objects in setState was getting updated, making it async returned functionality back and now both objects are correctly updating. I have freaking no idea what the hell was wrong.Geezer
I found this question, but this solution didn't work for me, I didn't solve my problem yet but I found a good read here: ozmoroz.com/2018/11/why-my-setstate-doesnt-workCripps
@OndřejŠevčík most likely a race-condition makes it looks like it works, but it'll possibly break again someday.Unpolitic
Calling await on a non-promise is the equivalent of calling this.setState(..); await Promise.resolve(); which has the side effect of queueing all subsequent statements for immediate execution on the microtask queue. You could push out execution further by using the traditional event loop with an await new Promise(rs => setTimeout(rs, 0)) but even, still, this is a hack, at best.Autocatalysis
ho to do that in functinal component?Cupel
@Cupel you need to use HooksHarley
T
2

Although there are many good answers, if someone lands on this page searching for alternative to useState for implementing UI components like Navigation drawers which should be opened or closed based on user input, this answer would be helpful.

Though useState seems handy approach, the state is not set immediately and thus, your website or app looks laggy... And if your page is large enough, react is going to take long time to compute what all should be updated upon state change...

My suggestion is to use refs and directly manipulate the DOM when you want UI to change immediately in response to user action.

Using state for this purspose is really a bad idea in case of react.

Transcurrent answered 23/3, 2021 at 17:9 Comment(0)
M
2

The above solutions don't work for useState hooks. One can use the below code

setState((prevState) => {
        console.log(boardAddModalShow)
        // call functions
        // fetch state using prevState and update
        return { ...prevState, boardAddModalShow: true }
    });
Maverick answered 24/10, 2021 at 18:3 Comment(1)
This makes no sense to me. I cannot translate it to my app. All I want to do is setMyVariable(false) hook variable immediately. Why is this so hard?Malka
L
1

setState() is asynchronous. The best way to verify if the state is updating would be in the componentDidUpdate() and not to put a console.log(this.state.boardAddModalShow) after this.setState({ boardAddModalShow: true }) .

according to React Docs

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately

Lantha answered 3/3, 2019 at 7:55 Comment(0)
F
1

According to React Docs

React does not guarantee that the state changes are applied immediately. This makes reading this.state right after calling setState() a potential pitfall and can potentially return the existing value due to async nature . Instead, use componentDidUpdate or a setState callback that is executed right after setState operation is successful.Generally we recommend using componentDidUpdate() for such logic instead.

Example:

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      counter: 1
    };
  }
  componentDidUpdate() {
    console.log("componentDidUpdate fired");
    console.log("STATE", this.state);
  }

  updateState = () => {
    this.setState(
      (state, props) => {
        return { counter: state.counter + 1 };
      });
  };
  render() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <button onClick={this.updateState}>Update State</button>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Finally answered 18/5, 2019 at 7:55 Comment(0)
I
-1

when i was running the code and checking my output at console it showing the that it is undefined. After i search around and find something that worked for me.

componentDidUpdate(){}

I added this method in my code after constructor(). check out the life cycle of react native workflow.

https://images.app.goo.gl/BVRAi4ea2P4LchqJ8

Icelander answered 22/12, 2016 at 8:3 Comment(0)
P
-1
 this.setState({
    isMonthFee: !this.state.isMonthFee,
  }, () => {
    console.log(this.state.isMonthFee);
  })
Predicant answered 12/4, 2019 at 6:42 Comment(1)
This is exactly what the most upvoted answer explains, but without any explanation and 3 years late. Please do not post duplicate answers unless you have something relevant to add to it.Unpolitic
V
-2

Yes because setState is an asynchronous function. The best way to set state right after you write set state is by using Object.assign like this: For eg you want to set a property isValid to true, do it like this


Object.assign(this.state, { isValid: true })


You can access updated state just after writing this line.

Value answered 28/8, 2019 at 10:20 Comment(1)
Don't do this, it mutates the current state, which is an anti-pattern.Unpolitic

© 2022 - 2024 — McMap. All rights reserved.