3

I have a function that does some animation using async await(). I want to play/pause the animation onClick. I used useState to update the variable but the function is not behaving as I want. While the const print () is running and when I click on the button, text updates, but the function doesn't break. In the log, "play" is printed 100 times. I'm guessing useState and async is causing something. How can I solve this?

const Test= props => {

    const [ButtonText, SetButtonText] = useState("Play");
    const ToggleButtonText = () => {
        if(ButtonText == "Play")          
            SetButtonText("Pause")

        if(ButtonText == "Pause")
            SetButtonText("Play")
    }

    const delay = ms => new Promise(res => setTimeout(res, ms));

    const print= () => {
        for(var i = 0; i < 100; i++)        
        {
            ///
               work
               await delay(1000);
               work
            ///
           console.log(ButtonText);
           
           if(ButtonText == "Pause")
               break ; 

        }
    };
    return (
           <div>
              <div>
                   ///work component///
              </div>
            <button onClick = {() => ToggleButtonText()}> {ButtonText}</button>
           </div>
    )
}

Ryu
  • 47
  • 5
  • Please use lowercase for variables. Capitals are for classes and components. Rejecting this convention makes the code unnecessarily hard to understand. – ggorlen Sep 14 '22 at 16:35

1 Answers1

3

The ButtonText is declared as const - it can't change its value inside a given call of the functional component.

Without more info, I'd use a ref as well as state, so that the loop can see if the ref gets changed:

const buttonTextRef = useRef();
// Update the ref to ButtonText every time ButtonText changes:
useEffect(() => {
  buttonTextRef.current = ButtonText;
}, [ButtonText]);
if (buttonTextRef.current === 'Pause') {
  break;
}

Another method would be to put the index being iterated over into state, and instead of looping 100 times, re-render 100 times, eg, something along the lines of:

const [printI, setPrintI] = useState(0);
if (printI) {
  // Then we're in the middle of iterating
  // work
  if (printI < 100) {
    setTimeout(() => {
      setPrintI(printI + 1);
    }, 1000);
  } else {
    setPrintI(0);
  }
};

and to stop it, call setPrintI(0).

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • I'm trying the first method, buttonTextRef, the variable is not updating inside the loop. i have print `console.log(buttonTextRef.current+ " " +ButtonText ); ` after await to check, each time it prints `play play `. I tried `var` instead of `const` but still not working. – Ryu Dec 28 '20 at 05:26
  • Are you sure you used the `useEffect` in the answer? If implemented properly, it will update the ref. – CertainPerformance Dec 28 '20 at 05:29
  • I think I used it properly. since I'm using `await`I have to declare the function as `const print= async () =>` that's all I added extra. In `SetButtonText` function both `ButtonText` and `buttonTextRef` are updating properly. – Ryu Dec 28 '20 at 05:39
  • Can you edit a live snippet into the question illustraing the problem, or a link to a codesandbox or something? `buttonTextRef.current = ButtonText;` *really really should* update the ref properly every time `ButtonText` updates, it sounds like there's something else going on – CertainPerformance Dec 28 '20 at 05:43
  • 1
    here codesandbox link : https://codesandbox.io/s/withered-water-euo1q?file=/src/App.js – Ryu Dec 28 '20 at 05:50
  • Oh man. Typo on my part. `const buttonTextRef = useRef();`, not `useState` – CertainPerformance Dec 28 '20 at 05:51