9

I want my aysnc function to react to state change so that it can terminate upon state change. What I have discovered is that it doesn't react to changing the state.

In the provided example I am changing the state from inside of the function, however I have tested it and changed the state from outside of function (Button click) and it doesn't work either.

const [testVariable, setTestVariable] = useState(false);

useEffect(()=> {
  testFunction();
}, [])

const testFunction = async () => {
  await timeout(1000);

  console.log("Setting test variable to: " + true);
  setTestVariable(true);

  await timeout(1000);
  
  console.log("Test variable is: " + testVariable);
}

Expected result is to see that the variable changed to true, however what I see is: enter image description here

5 Answers 5

13

State variables never change their values. (Note that you can, have, and should declare them with const because they don't change).

Subsequent invokes of the component function will get a new state variable with the same name and a different value.

Your testFunction function has closed over the old state variable.


I suspect you could solve this with a reference:

const [testVariable, setTestVariable] = useState(false);
const reference = useRef();
reference.current = testVariable;

… and then have your function test the value of reference.current instead of testVariable.


This does, however, feel like an XY Problem where the real solution is "Use the return value from the function you pass to useEffect to stop whatever it is you want to stop" (and use testVariable as a dependency of useEffect).

Sign up to request clarification or add additional context in comments.

3 Comments

@Quentin yes. Sorry but I have deleted my comment. For those who seek a TypeScript solution - you just need to use useRef<VariableType>(), where VariableType is whatever your useState holds. For example boolean
This is not the proper use case for useRef - reactjs.org/docs/hooks-reference.html#useref. useRef should be used to pass stateful variables from a child component to a parent component. (example: a detailed view of an element has a ref to the value of the same element in a parent list, to update the parent based on the changed properties).
@KimBrunJørgensen — Please read the link you just shared more carefully. It says "A common use case is to access a child imperatively". Common does not mean "only acceptable". It also says "However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around". Also note the final paragraph of my answer where I suggested that a better solution probably existed but the question didn't provide enough information about the real problem.
4

state update in react is async means that your state will be changed in next render cycle. you can useEffect hook to track if any state is changed or not.

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

3 Comments

Yes I am aware of that but I need to terminate a for inside an async function on state change
In that case, you should store the new value of testVariable in a const inside the async function and use that as the reference for the for loop.
This is the best answer for the OP's snippet. Using a ref is a workaround that might be needed in a special case.
0

Change it to

const [testVariable, setTestVariable] = useState(false);

useEffect(()=> {
  testFunction();
}, [])

const testFunction = async () => {
  await timeout(1000);

  console.log("Setting test variable to: " + true);

  let newTestVariable = true;  
  setTestVariable(newTestVariable);

  await timeout(1000);
  
  console.log("Test variable is: " + newTestVariable);
}

Changing the state variable is an asynchronous operation and works with something called the event loop. It will not immediately get updated when you did setTestVariable(true).

1 Comment

No point in the state variable at all in this case.
0

I had the same issue solved it by adding anonymous function inside setState.

setState((state) => state="value")

if you are toggling Boolean

setState((state) => !state)

Comments

-1

The async function is changing the state, but you're logging testVariable in the same lifecycle, which is why it looks as though the state wasn't changed.

If you instead change the code to be

const [testVariable, setTestVariable] = React.useState(false)

console.log('testVariable', testVariable)
React.useEffect(() => {
  testFunction()
}, [])

const testFunction = async () => {
  await timeout(1000)
  setTestVariable(!testVariable)
}

return <h2>{testVariable}</h2>

It will log the output:

testVariable false
testVariable false
testVariable true
testVariable true

The reason it's showing two logs for both false and true is because the console.log is outside the React.useEffect(() => {}, []) (which is equivalent to componentDidMount for stateful React components). React is first executing the console.log before the component mounts, then after the component mounts it executes the code inside React.useEffect and rerenders the component, thus executing the console.log statement again.

To only log once you would have to change the React.useEffect to:

React.useEffect(() => {
  console.log('testVariable', testVariable)
  testFunction()
}, [testVariable])

Adding a properties to the array arguments [testVariable], changes the React.useEffect from being a componentDidMount equivalent function to be a function chat executes on componentDidMount and for every new state of testVariable.

1 Comment

Why was this downvoted? Looks right to me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.