Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is ref value incrementing twice

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```
like image 588
Abhi Avatar asked Nov 05 '22 23:11

Abhi


1 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.

like image 129
Giorgi Moniava Avatar answered Nov 15 '22 09:11

Giorgi Moniava