useState with boolean value in react

Viewed 53615

In the code snippet below, when I click on Change button to change the value of isLoading , nothing happened (isLoading is false).

const App = (props) => {
  const [isLoading, setIsLoading] = useState(false)

  const buttonHandler = () => {
    setIsLoading(current => !current)
    console.log(isLoading) // is false 
  }

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

I tried to change isLoading with the following ways but does not affect:

1-setIsLoading(current => !current)
2-setIsLoading(!isLoading)
3-setIsLoading(true)
4 Answers

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>

State and hooks are asynchronous. You won't see a change in isLoading directly after calling set..., but only on the next render of the component, which will happen "soon enough".

If you do print the value of the statelet (as a string; false renders as nothing), you can see the change:

return (
    <div>
      <button onClick={buttonHandler} type="button">
        Change (now {"" + isLoading})
      </button>
    </div>
  )
const [isLoading, setIsLoading] = useState(false)

Don't use useState like this, you cannot get the state value immediately after update.

Reference: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

So, instead of this use: useRef (If you want to keep the object for the full lifetime of the component).

i.e.

const isLoading = useRef(false);

And later you can assign it as:

isLoading.current = true;

And you can use it as:

console.log(isLoading.current);

Reference: https://reactjs.org/docs/hooks-reference.html

Related