I understand that setState is asynchronous, but does it explicitly wait for all other code to be executed before it completes (even setTimeout with delay)?
I’ve been trying to increment a counter using a simple recursive function in React, but setState never executes. I added a setTimeout with a one-second delay, just to give setState enough time to execute, but it just keeps waiting infinitely and never executes.
Is replacing useState with useRef the best solution, or are there more elegant solutions?
import { useState } from 'react';
const TestComponent = () => {
const [counter, setCounter] = useState(1);
const setStateTest = () => {
setCounter(prev => prev + 1); // this line of code never executes
setTimeout(() => {
if (counter < 10) {
setStateTest(); // this recursion happens infinitely because counter is never incremented
console.log(counter); // the logged value is always 1, no incrementing
}
}, 1000)
}
return <button onClick={setStateTest}>Set State Test</button>
}
2
Answers
Don’t use recursion for this.
I suspect the problem is a closure around the original
counter
value. State itself may be getting updated, but you’re not logging the current state to the console. You’re logging the captured value of that state when the function was first called. (Click the button again and observe updated values being logged to the console, alongside the ongoing original value.)Instead of recursing, rely on
useEffect
. For example, consider this approach:Any time
counter
changes, this effect will execute. And all this effect does is:counter
to the consoleThen to initially trigger it, just update state once:
Once this changes the state, the effect triggers and it perpetually re-triggers itself every second (by updating the state value in its dependency array) as long as the component is loaded.
States in React are asynchronous and the value will only update after the function has finished running. Since your function never stops running (as it loops the
setTimeout
), the asynchronous call never gets a chance to finish and update the state value, thus it remains at 1. For this reason, state is not the best way to do what you are currently trying to do. Define counter usinglet
outside of the scope ofTestComponent
instead.