avocodoo
avocodoo

Reputation: 57

React How to handle recursive API call with setTimeout

I'm making an API call and if it fails, I need to call the API again after a set amount of time, however, I'm having trouble clearing the timeout due to the scope of the timeout.

This is working, but the timeout is able to be cleared:

React.useEffect(() => {
if (progress === 45 && !apiCallFailedRef.current) {
  callApi();
}

function callApi(){
    let requestData = {
      method: 'GET',
      path,
    };

    $.ajax(testPath, {
      method: 'GET',
      data: { data: JSON.stringify(requestData) },
    })
      .done(response => {
        // setLoading(false);
      })
      .fail(response => {
        console.log('failed')
        apiCallFailedRef.current = true;
        setTimeout(() => callApi(), 28000);
      });
  }
};

// How would I clear the timeout correctly?
// return () => {
//   clearTimeout();
// };
}, [progress])

Upvotes: 1

Views: 1756

Answers (3)

Jenia Enakin
Jenia Enakin

Reputation: 43

You can write a timeout to a ref and create a separate useEffect hook to clean up it only once on unmount.

// create a ref
const timeoutRef = useRef();

// in your useEffect
timeoutRef.current = setTimeout(() => callApi(), 28000);

// clean up useEffect
useEffect(() => {
  return () => {
    clearTimeout(timeoutRef.current);
  }
}, []);

Upvotes: 1

corypis
corypis

Reputation: 81

I would recommend not trying to retry forever, you can use a specific number of retries, and each retry can also wait a little bit longer before it retries.

This would make an api call, retrying up to 5 times

function callApiWithRetry(url, options = {}, numRetries = 5, backoff = 250) {

    return fetch(url, options)
      .then(res => {

        // if successful, return JSON response
        if (res.ok) {
          return res.json();
        }
        
        // not successful, retry until numRetry is 0
        if (numRetries > 0) {
          // make the api call again, but reduce the retry count, and double the backoff time
          setTimeout(() => {
            return callApiWithRetry(url, options, numRetries - 1, backoff * 2)
          }, backoff);
        } else {
          // out of retries, something is wrong
          throw new Error(res);
        }
      })
      .catch(console.error);
}

Upvotes: 0

ale917k
ale917k

Reputation: 1768

You can assign the timeout to a variable as such:

React.useEffect(() => {
  let timeout;

  if (progress === 45 && !apiCallFailedRef.current) {
    callApi();
  }

  function callApi(){
    let requestData = {
      method: 'GET',
      path,
    };

    $.ajax(testPath, {
      method: 'GET',
      data: { data: JSON.stringify(requestData) },
    })
      .done(response => {
        // setLoading(false);
      })
      .fail(response => {
        console.log('failed')
        apiCallFailedRef.current = true;
        timeout = setTimeout(() => callApi(), 28000);
      });
    }
  };

  // How would I clear the timeout correctly?
  return () => {
    clearTimeout(timeout);
  };
}, [progress])

Upvotes: 0

Related Questions