Javascript binds variables to functions as they are created, which can cause ‘dangling’ out-of-date values to be used in useEffect.
function Counter() {
var [count, setCount] = useState(1);
useEffect(() => {
var timeout = setInterval(() => setCount(count => count+1), 1000); // every 1'
setTimeout(() => console.log(`Count after 10': ${count}`), 10000); // after 10'
return () => clearTimeout(timeout);
}, []);
return <span>{count}</span>
}
The anonymous function of setInterval binds to count when it’s 1, and therefore the console.log output isn’t 10 (but 1).
Is there an elegant fix? For example, adding count as a dependency of useEffect would re-create the timeout in this case, causing unwanted behavior – although it would be a fix in many similar use cases.
Can I pass count by reference somehow without the boilerplate of wrapping it in an object and needing to keep that object in sync?
>Solution :
You can use a useRef hook to grab the current value of count when your timer runs out.
function Counter() {
var [count, setCount] = useState(1);
var countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count])
useEffect(() => {
var timeout = setInterval(() => setCount(count => count+1), 1000); // every 1'
setTimeout(() => console.log(`Count after 10': ${countRef.current}`), 10000); // after 10'
return () => clearTimeout(timeout);
}, []);
return <span>{count}</span>
}
Although at that point, it might be easier to drop the state
function Counter() {
var countRef = useRef(1);
useEffect(() => {
var timeout = setInterval(() => countRef.current = countRef.current + 1, 1000); // every 1'
setTimeout(() => console.log(`Count after 10': ${countRef.current}`), 10000); // after 10'
return () => clearTimeout(timeout);
}, []);
return <span>{countRef.current}</span>
}