setIsLoading is an async function and you cannot get the state value immediately after update.

setState actions are asynchronous and are batched for performance gains. setState() does not immediately mutate this. Thus the setState calls are asynchronous as well as batched for better UI experience and performance. This applies on both functional/Class components.

From React documentation

React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. You could read more about this here

If you want to get the updated state value then use useEffect hook with dependency array. React will execute this hook after each state update.

const {useEffect, useState } = React;

const App = (props) => {
  const [isLoading, setIsLoading] = useState(false)
  const buttonHandler = () => {
    setIsLoading(current => !current)
  }

  useEffect( () => {
    console.log(isLoading);
}, [isLoading]);

  return (
    <div>
      <button onClick={buttonHandler} type="button">
        Change
      </button>

      {isLoading? "Loading...": null}
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

    <div id="root">
      loading.....
    </div>

Answer from Sohail Ashraf on Stack Overflow
🌐
Reddit
reddit.com › r/reactjs › usestate(boolean) vs usestate(false)
r/reactjs on Reddit: useState(Boolean) vs useState(false)
February 18, 2021 -

Is it a thing to set default state values using primitive type constructors when applicable? e.g.

// Boolean (defaults to false)
const [active, setActive] = useState(Boolean);
const [active, setActive] = useState(false);

// String (defaults to "") 
const [searchTerm, setSearchTerm] = useState(String); 
const [searchTerm, setSearchTerm] = useState("");

// Number (defaults to 0)
...

Another developer who was learning React at the same time as me said I should do it this way and I pretty much have been doing it this way for the last 2 years.

I figured it helped me write more declarative code and never gave it much more thought until this week. I've been trying to find any information discussing this but haven't found a thing.

Is there any explanation for or against this that people can think of? Do many other people declare state like this?

Top answer
1 of 6
25

setIsLoading is an async function and you cannot get the state value immediately after update.

setState actions are asynchronous and are batched for performance gains. setState() does not immediately mutate this. Thus the setState calls are asynchronous as well as batched for better UI experience and performance. This applies on both functional/Class components.

From React documentation

React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. You could read more about this here

If you want to get the updated state value then use useEffect hook with dependency array. React will execute this hook after each state update.

const {useEffect, useState } = React;

const App = (props) => {
  const [isLoading, setIsLoading] = useState(false)
  const buttonHandler = () => {
    setIsLoading(current => !current)
  }

  useEffect( () => {
    console.log(isLoading);
}, [isLoading]);

  return (
    <div>
      <button onClick={buttonHandler} type="button">
        Change
      </button>

      {isLoading? "Loading...": null}
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

    <div id="root">
      loading.....
    </div>

2 of 6
4

This is the expected behavior. You may want to use useEffect to access the latest value.

Here is a thread discussing the same issue: useState set method not reflecting change immediately

Hope this helps!

🌐
CodeSandbox
codesandbox.io › s › usestate-boolean-basic-example-iepcl
useState boolean basic example - CodeSandbox
January 30, 2020 - useState boolean basic example by sandagolcea using react, react-dom, react-scripts
Published   Jun 10, 2019
Author   sandagolcea
🌐
usehooks-ts
usehooks-ts.com › react-hook › use-boolean
useBoolean | usehooks-ts
Will throw an error if defaultValue is an invalid boolean value. ... The useBoolean return type. import { useCallback, useState } from 'react' import type { Dispatch, SetStateAction } from 'react' type UseBooleanReturn = { value: boolean setValue: Dispatch<SetStateAction<boolean>> setTrue: () => void setFalse: () => void toggle: () => void } export function useBoolean(defaultValue = false): UseBooleanReturn { if (typeof defaultValue !== 'boolean') { throw new Error('defaultValue must be `true` or `false`') } const [value, setValue] = useState(defaultValue) const setTrue = useCallback(() => { setValue(true) }, []) const setFalse = useCallback(() => { setValue(false) }, []) const toggle = useCallback(() => { setValue(x => !x) }, []) return { value, setValue, setTrue, setFalse, toggle } }
🌐
DEV Community
dev.to › alexkhismatulin › update-boolean-state-right-with-react-hooks-3k2i
Update boolean state right with React Hooks - DEV Community
May 11, 2020 - I will create two separate state handlers: one for our boolean state and another one for storing a random number. const BasicBooleanState = () => { const [isToggled, setIsToggled] = React.useState(false); const toggle = React.useCallback(() => setIsToggled(!isToggled)); const [randomNumber, setRandomNumber] = React.useState(Math.random()); const generateRandomNumber = React.useCallback( () => setRandomNumber(Math.random()), [], ); return ( <div> <div> Current random number is <b>{randomNumber}</b> <button style={{ marginLeft: '10px' }} onClick={generateRandomNumber}> regenerate </button> </div> <div> Boolean is set to <b>{String(isToggled)}</b>. </div> <RendersCounter onClick={toggle} /> </div> ); }
🌐
Selftaughttxg
selftaughttxg.com › 2023 › 04-23 › creating-a-true-false-toggle-in-react-with-usestate-hook-for-beginners
Creating a True/False Toggle in React with useState Hook for Beginners |
April 14, 2023 - By using the useState hook, you can manage state changes in a way that's easy to understand and debug. The HanSolo component example not only demonstrates how to create a true/false toggle using the React useState hook but also addresses the age-old controversy of whether Han Solo shot first or not!
🌐
Bobby Hadz
bobbyhadz.com › blog › react-toggle-boolean-state
How to toggle a Boolean state in React | bobbyhadz
Here is an example of how to toggle boolean state and make an HTTP request only when the state is equal to true. ... Copied!import {useEffect, useState} from 'react'; export default function App() { const [isActive, setIsActive] = useState(false); const [data, setData] = useState({data: []}); const toggleIsActive = () => { setIsActive(isActive => !isActive); }; useEffect(() => { console.log('isActive is: ', isActive); if (isActive) { fetchData(); } else { setData({data: []}); } }, [isActive]); const fetchData = async () => { const response = await fetch('https://reqres.in/api/users', { method:
🌐
React
react.dev › reference › react › useState
useState – React
It only affects what useState will return starting from the next render. 1. Counter (number) 2. Text field (string) 3. Checkbox (boolean) 4. Form (two variables) In this example, the count state variable holds a number.
Find elsewhere
🌐
YouTube
youtube.com › techinfo yt
useState Boolean Value Issue | React Hooks Tutorials - YouTube
video contains: solution of Boolean value with useState HookWelcome to React JS Hooks Series in this video you will learn about complete react js hooks with...
Published   March 11, 2022
Views   2K
🌐
Medium
medium.com › @IkeoluwaAshade › clicking-button-to-toggle-a-boolean-state-8257b70fb293
Clicking button to toggle a Boolean state | by Ikeoluwa Ashade | Medium
June 20, 2024 - toggle is the current state variable, which is a boolean (i.e true or fasle). !toggle is the negation of toggle. If toggle is true, !toggle will be false and vice versa ... setToggle(!toggle) updates the state to the opposite of its current value. In react, invoking (i.e calling) a state setter function (i.e setToggle) causes the component to re-render with the new state value. ... import React, { useState } from 'react'; const ToggleComponent = () => { // Declare a state variable named "toggle" with an initial value of false const [toggle, setToggle] = useState(false); ...
🌐
Jstopics
jstopics.com › reactjs › usestate-with-typescript
UseState with TypeScript | JSTopics
This variable holds data (boolean) that tell our component if it should show a modal. This value usually changes on user input. The user clicks on the button and the modal will be shown. Let's look at a basic state example: ... import { useState } from 'react' function App() { const [showModal, ...
🌐
Dommagnifi
dommagnifi.co › 2020-12-03-toggle-state-with-react-hooks
Toggle State With React Hooks - Dominic Magnifico
n1 = !!true // !!truthy returns true n2 = !!{} // !!truthy returns true: any object is truthy... n3 = !!(new Boolean(false)) // ...even Boolean objects with a false .valueOf()! n4 = !!false // !!falsy returns false n5 = !!"" // !!falsy returns false n6 = !!Boolean(false) // !!falsy returns false · This section was mostly for my own edification. I wasn't 100% clear on the intent of the double not operator, but now we know! Let's use the logical not operator in our example to set the opposite value of the current state in our toggle button. import React, { useState } from 'react' const MyComponent = () => { const [toggle, setToggle] = useState(false) return( <> <button onClick={() => setToggle(!toggle)}>Toggle State</button> </> ) } Nice!
🌐
Dave Ceddia
daveceddia.com › usestate-hook-examples
4 Examples of the useState Hook
July 12, 2020 - But with hooks, the state can be ... a boolean, a string, whatever you need. Each call to useState creates a single piece of state, holding a single value of any type. The useState hook is perfect for local component state, and a small-ish amount of it. For a larger app, or one that you intend to scale up, you’ll probably want to augment useState with some other state management solutions. This example is a component ...
Top answer
1 of 6
12

Since, as often in code, a simple example paints a thousand words, here's a simple CodeSandbox demo to illustrate the difference, and why, if you want an update based on the value of the state at the point of update, the "updater function" (Approach 1) is best:

https://codesandbox.io/s/stack-overflow-demo-nmjiy?file=/src/App.js

And here's the code in a self-contained snippet:

<div id="root"></div><script src="https://unpkg.com/[email protected]/umd/react.development.js"></script><script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">

function App() {
  const [count, setCount] = React.useState(0);

  // this uses the "good way" but it doesn't really matter here
  const incrementPlain = () => setCount((oldCount) => oldCount + 1);

  const incrementWithTimeoutBad = () =>
    setTimeout(() => setCount(count + 1), 3000);
  const incrementWithTimeoutGood = () =>
    setTimeout(() => setCount((oldCount) => oldCount + 1), 3000);

  return (
    <div>
      <div>Current count: {count}</div>
      <div>
        <button onClick={incrementPlain}>
          Increment (doesn't matter which way)
        </button>
      </div>
      <div>
        <button onClick={incrementWithTimeoutBad}>
          Increment with delay (bugged)
        </button>
        <button onClick={incrementWithTimeoutGood}>
          Increment with delay (good)
        </button>
      </div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));

</script>

Here we have a simple numeric "count" state which is displayed in the markup, together with 3 different buttons that all increment it.

The one on top just does a direct increment - I happen to have used the function form ("approach 1") here because I prefer this style for reasons that will hopefully become clear, but as my comment says, it doesn't actually matter here.

The two below use the two different approaches you outline in the question, and do so after a delay. I've done this with setTimeout here just to be simple - while this isn't particularly realistic, similar effects are commonly seen in real apps where actions call an API endpoint (and even though one hopes that doesn't normally take as long as 3 seconds, the same problems can always be observed with quicker requests - I've just slowed it down to be easier to trigger the bug).

To see the difference, try the following with each of the 2 bottom buttons:

  • click the button
  • click the button on top (to increment the count again) BEFORE the 3-second timeout is up

You should see a clear difference in behaviour:

  • with "approach 1" (button on the right, which I'm calling "good" here), the count increments a second time after the timeout is finished.
  • with "approach 2" (button on the left, which I've called "bugged"), there is no further increment from the value produced by the intermediate click on the top button, no matter how long you wait

(You can see this more dramatically if you click the bottom button multiple times quickly, then the top one once. And for an even more counterintuitive effect, try pressing the "bugged" bottom button one or more times, then clicking the top button more than once, all within the 3 second time interval.)

Why does this happen? Well, the "buggy" behaviour happens because the function inside the setTimeout is a closure over the outer variable count which is in the scope of the full component function. That means that when it's called with count + 1 as the argument, it will update the count to 1 more than whatever it was at the point the function was defined. Say you do the above sequence from first loading the component where count is 0, then the more detailed sequence of what happens is:

  • the bottom button click schedules a callback to happen in 3 seconds' time. Since count at that point is equal to 0, its argument, count + 1 is equal to 1.
  • the top button click rerenders the component, with count now equal to 1.
  • the callback set up at the first step later triggers, and sets the count to 1. This doesn't cause any noticeable chance, because the count was already 1. (If you tried clicking the top button multiple times, so it now shows 2 or more, this will actually decrement the counter, because as I'm explaining, it will always get set to 1.)

If you know a little bit about JS closures, you might wonder why the count that is accessed in the closure is still 0. Wasn't it previously updated to 1? No, it wasn't, and that's the bit that might be counterintuitive. Notice how count is declared with const? That's right - it never actually changes. The reason the UI updates is because setCount causes React to rerender your component, which means the whole outer function corresponding to the component is called again. This sets up a whole new environment, with a new count variable. React's internals ensure that the useState call now gives back 1 for the current count, which is therefore the value in that new "instance" of the component - but that's irrelevant from the point of view of the function that was put in the event queue to fire after 3 seconds. As far as it's concerned, the count variable - no longer in scope but "remembered" inside that callback as all closed-over variables are - has never changed from 0. The count that's equal to 1 is in a different scope entirely, and forever inaccessible to that first callback.

How does the function argument form - "approach 1" - get round this? Very easily. It doesn't hold any closure at all - the variable inside that function, which I've called oldCount here for the sake of both accuracy and to disambiguate from the outer count - has nothing to do with the count outside. It's the argument to a function that React itself will call internally. When React does call the function, it always supplies the "most up-to-date" state value it has. So you don't have to worry about "stale closures" or anything like that - you're saying "whatever the most recent value was, update the count to be one more than that", and React will take care of the rest.

I've called approach 2 "bugged" here because I think it's reasonable to expect an increment to happen after the timeout, if you've clicked a button that you've set up to do an increment. But this isn't always what you want. If you genuinely wanted the update to be based on the value at the point the button was first clicked, then of course you will prefer Approach 2, and Approach 1 will seem bugged. And in a sense that's more often the case. I highly recommend reading this post by Dan Abramov - one of the core React developers - that explains a crucial difference between class components and functions, that's based on many of the same arguments about closures that came into play here, where normally you do want the event handlers to reference values as they were at the time of render, not when they actually fire after an API request or timeout.

But that post doesn't have anything to do with the "approach 1" form of state-updating functions, which isn't even mentioned in the article. That's because it's irrelevant to the examples given - there'd be no (sensible) way to rewrite those examples to use it. But when you do want to update a state value based on its previous value - as could happen with negating a boolean value, as in the OP example, or incrementing a counter as in mine, I would argue that it's more natural that you always want that "previous value" to be up to date. There are 2 buttons which both should increment a value, albeit in different ways - I think it's reasonable to call it bugged if clicking both of them, depending on the timing, may only increment once in total.

But that's of course up to each individual component or application to decide. What I hope I've done here is explain what the difference is, and give you a basis to choose which might be best. But I do believe that 90+% of the time, if you have the option of using the function argument ("approach 1"), it will be better, unless you know it isn't.

2 of 6
6

the first approach setIsChanged((prevState) => !prevState)

to make sure that you always have the last state before changing it.

🌐
Linguine Code
linguinecode.com › home › blog › how to use set types on react usestate with typescript
How to use set types on React useState with TypeScript
September 11, 2020 - In today’s short article, I will ... number; hobbies: Array<string>; isCool: boolean; } // Boolean type const [isCool] = React.useState<boolean>(true); // String type const [name] = React.useState<string>('Ruben'); // Number ...
🌐
Medium
medium.com › @glasshost › how-to-toggle-a-boolean-state-in-react-9f2d0a935bb8
How to Toggle a Boolean State in React | by Glasshost | Medium
April 24, 2023 - import React, { useState } from ... export default App; In this example, we are creating a boolean state called `isToggled` and initializing it to `false` using the useState hook....
🌐
CodingDeft
codingdeft.com › posts › how-to-use-react-usestate-with-typescript
How to add type to useState while using TypeScript with React | CodingDeft.com
July 30, 2022 - If you need to declare a state to store a boolean value, then you can declare it as shown below: 1const [autoPlay, setAutoPlay] = useState<boolean>(false)
🌐
Josh W. Comeau
joshwcomeau.com › snippets › react-hooks › use-toggle
Toggle state on and off with React Hooks • Josh W. Comeau
import React from 'react'; export default function useToggle(initialValue = false) { const [value, setValue] = React.useState(initialValue); const toggle = React.useCallback(() => { setValue(v => !v); }, []); return [value, toggle]; } There are times when we want some React state that should always hold a boolean value, and should only allow toggling between true and false.