Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Use up-to-date state variable in `useEffect`

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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>
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading