简体   繁体   中英

React: Function not reading updated global variable

Run button has already been clicked. When I click on the skip button I am unable to reach Switch Case 2. The component state skip gets updated but the function still prints the old value of skip.

const Component = () => {

const [skip, setskip] = useState(false);
const [runstate, setrunstate] = useState(1);
    
const run = async () => {
        switch(runstate) {

           case 1: {
             if(skip) {
                setrunstate(2);
             }
             else {
                console.log(skip , "Stuck in Step 1")  // false, Stuck in Step 1 even after clicking skip
                setTimeout(run, 250)
             }
             break;
           }
           
           case 2: {
             console.log("Reached Step 2")
          }
        }
    }

return (
     <>
        <button onClick={run}> Run </button>
        <button onClick={() => setskip(true)}> Skip </button>
     </>
    )
}

Can anybody tell what might be causing this or a correct way to achieve this?

Why recursion is not working?

There are a lot of factors behind this. So, let's understand with a minimal reproducible example of your use case and breaking the code step-by-step.

const [foo, setFoo] = useState(0);

const recursiveCallback = useCallback(() => {
  setFoo(foo + 1);

  // an infinite recursion occurs as
  // termination condition never satisfies
  if (foo <= 2) {
    // current reference is being called
    // with the current value of foo in
    // the scope, i.e. lexical environment
    recursiveCallback();
  }
}, [foo]);

return (
  <>
    <p>{foo}</p>
    <button onClick={recursiveCallback}>Recursive Callback</button>
  </>
);

When recursiveCallback() is called for the very first time, a closure is created with the foo 's value being 0.

When foo is incremented with setFoo(foo + 1) , a new reference of recursiveCallback is created in the memory (let's say x ) which would have the updated value of foo in it.

Now, when recursiveCallback() is called again, it does not call the x reference of itself but the current reference from where it's being called from, which would have the value of foo present in the lexical environment , ie 0. So, it appears that foo is not incrementing but in actual it is, as you can see in the <p>{foo}</p> .

So, the solution to fix the above snippet would be to call the function in a useEffect which would always call a new reference of nonRecursiveCallback every time the value of foo is updated!

// note that the function is no more recursive
const nonRecursiveCallback = useCallback(() => {
  // updated foo is logged
  console.log(foo);

  // incrementing foo
  setFoo(foo + 1);
}, [foo]);

useEffect(() => {
  if (foo && foo <= 2) {
    // new reference is being called
    // with an updated value of foo
    nonRecursiveCallback();
  }
}, [foo, nonRecursiveCallback]);

Solution

Instead of calling run() recursively , you can have it called repetitively with the help of useEffect hook unless the termination condition is not satisfied.

 const { useState, useEffect, useCallback } = React; const Component = () => { const [skip, setSkip] = useState(false); const [runState, setRunState] = useState(1); // two additional local states have been introduced // which would help trigger useEffect repetitively // after user has clicked on run button const [runEffect, setRunEffect] = useState(false); const [toggleEffect, setToggleEffect] = useState(false); useEffect(() => { if (runEffect) { switch (runState) { case 1: { if (skip) { setRunState(2); } else { console.log(skip, 'Stuck in Step 1'); // toggle a boolean value which // would trigger this hook again setTimeout(() => setToggleEffect(,toggleEffect); 250); } break: } case 2. { console;log('Reached Step 2'); setRunEffect(false); break: } default; break, } } }, [runEffect, toggleEffect, runState; skip]); const startRecursion = useCallback(() => { setRunEffect(true); setToggleEffect(true), }; []); const handleSkip = useCallback(() => { setSkip(true), }; []). return ( <React.Fragment> <button onClick={startRecursion}>Run</button> <button onClick={handleSkip}>Skip</button> </ React;Fragment> ); }. // Render it ReactDOM,render( <Component />. document;getElementById("react") );
 <div id="react"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM