0

This is from another question that was asked by somebody, regarding state within a setInterval.

The question was why the state didn't update beyond 1. The answer was

The reason is because the callback passed into setInterval's closure only accesses the time variable in the first render, it doesn't have access to the new time value in the subsequent render because the useEffect() is not invoked the second time.

time always has the value of 0 within the setInterval callback.

Like the setState you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState callback to ensure that you have the latest state value before incrementing it.

function Clock() {
      const [time, setTime] = React.useState(0);
      React.useEffect(() => {
        const timer = window.setInterval(() => {
          setTime(prevTime => prevTime + 1);    <----this line was edited which makes the counter work according to the answer provided.
        }, 1000);
        return () => {
          window.clearInterval(timer);
        };
      }, []);
    
      return (
        <div>Seconds: {time}</div>
      );
    }
    
    ReactDOM.render(<Clock />, document.querySelector('#app'));

My question is, is it possible to use an if statement within that interval that uses the updated version of the time state? I tried console logging inside and outside of the interval and within the interval the counter doesn't update, while outside of it the state is updated. This makes sense given the previous answer but is there a way to use the updated version of the state inside the interval? Or perhaps a way around it?

For example maybe there is an if statement that uses the time state and a modulus operator with possibly another state, and plays a different sound according to the result of the calculation.

DJKI
  • 7
  • 2
  • You can see [this](https://stackoverflow.com/a/66435915/2873538) to understand more about this closure. If you need to, you can create a new custom hook - `useStateAndRef` (you can find an example in the other answer of the linked post). – Ajeet Shah Apr 08 '21 at 01:39

1 Answers1

0

It'd be possible to use the current stateful value in the callback, eg:

setTime(prevTime => {
  console.log(prevTime);
  // do other stuff with prevTime?
  return prevTime + 1;
});

Or even

setTime(prevTime => {
  // do stuff with prevTime
  return prevTime; // don't change state
});

But this is pretty weird. For the sake of code clarity, it'd be better to avoid side effects inside state setter functions.

Another problem is that the interval callback will only close over the initial state variables, so if you have a different state outside of time, it won't be updated in the interval's setTime.

If you want to do something like that, consider using a ref instead, which is like an instance variable for functional components.

const [time, setTime] = React.useState(0);
const [somethingStateful, setSomethingStateful] = useState(123);
const statefulRef = useRef(123);

Then, when somethingStateful gets updated, do both

setSomethingStateful(newVal);

and

statefulRef.current = newVal;

Then you'll be able to reference statefulRef.current inside the interval callback to get the most updated value.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320