polling api every x seconds with react

@AmitJS94, there's a detailed section on how to stop an interval that adds onto the methods that GavKilbride mentioned in this article.

The author says to add a state for a delay variable, and to pass in "null" for that delay when you want to pause the interval:

const [delay, setDelay] = useState(1000);
const [isRunning, setIsRunning] = useState(true);
  useInterval(() => {
    setCount(count + 1);
  }, isRunning ? delay : null);

    useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);

Definitely read the article to get a better understanding of the details -- it's super thorough and well-written!


Although an old question it was the top result when I searched for React Polling and didn't have an answer that worked with Hooks.

// utils.js

import React, { useState, useEffect, useRef } from 'react';

export const useInterval = (callback, delay) => {

  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);


  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      const id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

Source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/

You can then just import and use.

// MyPage.js

import useInterval from '../utils';

const MyPage = () => {

  useInterval(() => {
    // put your interval code here.
  }, 1000 * 10);

  return <div>my page content</div>;
}

Well, since you have only an API and don't have control over it in order to change it to use sockets, the only way you have is to poll.

As per your polling is concerned, you're doing the decent approach. But there is one catch in your code above.

componentDidMount() {
  this.timer = setInterval(()=> this.getItems(), 1000);
}

componentWillUnmount() {
  this.timer = null; // here...
}

getItems() {
  fetch(this.getEndpoint('api url endpoint'))
    .then(result => result.json())
    .then(result => this.setState({ items: result }));
}

The issue here is that once your component unmounts, though the reference to interval that you stored in this.timer is set to null, it is not stopped yet. The interval will keep invoking the handler even after your component has been unmounted and will try to setState in a component which no longer exists.

To handle it properly use clearInterval(this.timer) first and then set this.timer = null.

Also, the fetch call is asynchronous, which might cause the same issue. Make it cancelable and cancel if any fetch is incomplete.

I hope this helps.


You could use a combination of setTimeout and clearTimeout.

setInterval would fire the API call every 'x' seconds irrespective whether the previous call succeeded or failed. This can eat into your browser memory and degrade performance over time. Moreover, if the server is down, setInterval would continue to bombard the server not knowing its down status.

Whereas,

You could do a recursion using setTimeout. Fire a subsequent API call, only if the previous API call succeed. If previous call has failed, clear the timeout and do not fire any further calls. if required, alert the user on failure. Let the user refresh the page to restart this process.

Here is an example code:

let apiTimeout = setTimeout(fetchAPIData, 1000);

function fetchAPIData(){
    fetch('API_END_POINT')
    .then(res => {
            if(res.statusCode == 200){
                // Process the response and update the view.
                // Recreate a setTimeout API call which will be fired after 1 second.
                apiTimeout = setTimeout(fetchAPIData, 1000);
            }else{
                clearTimeout(apiTimeout);
                // Failure case. If required, alert the user.
            }
    })
    .fail(function(){
         clearTimeout(apiTimeout);
         // Failure case. If required, alert the user.
    });
}