Why is ref value incrementing twice

Viewed 172

I have this code here:

export default function App() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(0);
  const reff = useRef(0);

  useEffect(() => {
    
      const id = setInterval(() => {

      setCount((c) => {
        console.log({ step }, "reff.current before incr ", reff.current);
        reff.current = reff.current + step;
        console.log("ref.curret after incr ", reff.current);
        return c + step;
      });

    }, 6000);

    return () => clearInterval(id);
  }, [step]);


  return (
    <>
      <h1>Count:{count}</h1>
      <h1>Step: {step}</h1>
      <input
        type="button"
        onClick={(e) => setStep((prevStep) =>(prevStep + 1))}
        value="+ step"
      />
    </>
  );
}

You can view it here: https://codesandbox.io/s/gracious-roentgen-31ntb?file=/src/App.js:0-817

Note that I'm incrementing ref inside setCount callback

When the component loads a setInterval is started. If I trigger the useEffect again by incrementing step before 6 seconds pass, it clears the setInterval and creates a new one.

And now if I don't increment step again and wait for 6 seconds, I see step: 1 reff.current before incr, 0 for the first time the setInterval callback is called.

After incrementing in the next line(reff.current = reff.current + step) I see "ref.curret after incr ", 1

When the setInterval callback is called again after 6 seconds, I see

step: 1 reff.current before incr, 2 //how did this become 2

I don't understand how the value of reff.current is 2.

This only happens when I increment the step(which clears the first interval). If I set the initial step to 1 and don't increment it, I see expected values.. Just checked again. It doesn't work as expected.

I can't understand why the value of reff.current is 2 when the setInterval callback is called the second time.

Go to the sandbox link and

  1. Click on the +step button once

  2. Open the console and see

log 1:{step: 1} "reff.current before incr "0
log 2: ref.curret after incr 1
//second time callback is called
log 3: {step: 1} "reff.current before incr "2 //this should be 1
log 4: ref.curret after incr 3```
2 Answers

Your problem happens because you are performing a side effect (updating reff.current) inside the updater function passed to the setCount.

The beta react docs say that:

Updater functions run during rendering, so updater functions must be pure and only return the result. Don’t try to set state from inside of them or run other side effects. In Strict Mode, React will run each updater function twice (but discard the second result) to help you find mistakes.

You are running your app in StrictMode. To verify it comment it:

const rootElement = document.getElementById('root');
ReactDOM.render(
  // <StrictMode>
  <App />,
  // </StrictMode>,
  rootElement
);

and the problem doesn't happen anymore.

Can you please try out this code below,


Not sure about the exact reason why the value was incremented, but using it directly instead of returning from a function, did work as expected. Also it works for further step increment.


import { useEffect, useRef, useState } from "react";
import "./styles.css";

export default function App() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(0);
  const reff = useRef(0);
  useEffect(() => {
    const id = setInterval(() => {
      console.log({ step }, "reff.current before incr ", reff.current);
      debugger;
      //  ------ modified here
      let newVal = reff.current + step;
      reff.current = newVal;
      console.log("ref.curret after incr ", reff.current);
      setCount(newVal);
      //  ------ modified here
    }, 6000);
    return () => clearInterval(id);
  }, [step]);

  return (
    <>
      <h1>Count:{count}</h1>
      <h1>Step: {step}</h1>
      <input
        type="button"
        onClick={(e) => {
          e.preventDefault();
          setStep((prevStep) => {
            console.log(prevStep);
            return Number(prevStep) + 1;
          });
        }}
        value="+ step"
      />
    </>
  );
}

The image of the log logs

Related