React stop function with useState

I have a button with which I start a test function:

<button type="button" onClick={startTestFrequencyList}>{
  frequenciesTestRunning ? 'Stop Test Frequency' : 'Start Test Frequency'
}</button>

The startTestFrequencyList either starts the testFrequencyList or stops it.

const [stopTest, setStopTest] = useState(false);
const [frequenciesTestRunning, setFrequenciesTestRunning] = useState(false);
const startTestFrequencyList = () => {
  if (frequenciesTestRunning) {
    window.alert("Already testing")
    setStopTest(true);
  } else {
    setFrequenciesTestRunning(true);
    testFrequencyList();
  }
}

Inside the startTestFrequencyList function, I execute a lot of fetch calls in a loop which I want to stop if the user clicks the button again.

const testFrequencyList = async () => { 
  for (const item of frequencyList) {
    try {
      // Stop the fetching
      if (stopTest) {
        // STOP TEST
        stopTest(false);
        setFrequenciesTestRunning(false);
        return;
      }
      const response = await fetch('http://127.0.0.1:8000/frequency/testWithRe/', {
        method: 'POST',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(item),
      });
      const testResult = await response.json();
      // do something
    } catch (error) {
      console.error('Error:', error);
    }
  }
}

The problem is, whenever stopTest is set to true, nothing happens. It seems like the function does not get the newest state of stopTest.

  • But what is stopTest? It’d help to show the entire component (in minimal reproducible example form of course) so that it’s clear which each variable and function is.

    – 

  • Sorry. Added the stopTest state. The entire component is quite huge. I will try to reproduce an example

    – 

  • You’re creating a closure over stopTest. You could make it an object and mutate it? or use an abortController? see: Proper way to Abort (stop) running async/await function?

    – 




  • The value of a state variable wont change until the component re-renders. Not sure if that’s the problem here.

    – 

You can use useRef

const refStop = React.useRef(false)
  const startTest = React.useCallback(async () => {
    const arr = new Array(10000).fill(0).map((_,i) => i)
    for await (const i of arr ){
    await new Promise((resolve) => setTimeout(resolve, 1000));
      if (refStop.current) {
        refStop.current = (false)
        return console.log("stopped")
      }
      console.log("i", i)
    }
  },[refStop.current])
  return (
    <div className="App">
      <button onClick={startTest}>start</button>
      <button onClick={() => refStop.current = (true)}>stop</button>
    </div>
  );

But this will not abort the on-going promise. If you want to abort on going promises, you may want to use abort controllers

Leave a Comment