Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call API Every X Seconds in React Function Component

I have the following react class component to call an API every 10 seconds. Its works with no issues.

class Alerts extends Component {
  constructor() {
    this.state = {
      alerts: {},
    }
  }

  componentDidMount() {
    this.getAlerts()
    this.timerId = setInterval(() => this.getAlerts(), 10000)
  }

  componentWillUnmount() {
    clearInterval(this.timerId)
  }

  getAlerts() {
    fetch(this.getEndpoint('api/alerts/all"))
        .then(result => result.json())
        .then(result => this.setState({ alerts: result }))
  }

  render() {
    return (
      <>
        <ListAlerts alerts={this.state.alerts} />
      </>
    )
  }
}

I am trying covert this to a react functional component. This is my attempt so far.

const Alerts = () => {

    const [alerts, setAlerts] = useState([])

    useEffect(() => {
        getAlerts()
        setInterval(() => getAlerts(), 10000)
    }, [])

    getAlerts() {
        fetch(this.getEndpoint('api/alerts/all"))
            .then(result => result.json())
            .then(result => setAlerts(result)
    }

    return (
      <>
        <ListAlerts alerts={alerts} />
      </>
    )
}

Please can someone help me complete the example? Is useEffect the correct usage or is there a better option?

Any help would be appreciated

like image 945
hawx Avatar asked Dec 02 '19 20:12

hawx


2 Answers

One issue here is that this.getEndpoint will not work from a function component. It seems the original Alerts class component is missing some code since that must be implemented somewhere.

Another issue is that the interval is not being cleaned up - you should return a cleanup function from the effect body to clear the timer.

Lastly there's no reason to re-define getAlerts on every render, defining it once inside of the effect body would be better.

After cleaning up some missing parens, etc. my final implementation would look something like:

function getEndpoint(path) {
   return ...; // finish implementing this
}


const Alerts = () => {

    const [alerts, setAlerts] = useState([])

    useEffect(() => {
        function getAlerts() {
          fetch(getEndpoint('api/alerts/all'))
            .then(result => result.json())
            .then(result => setAlerts(result))
        }
        getAlerts()
        const interval = setInterval(() => getAlerts(), 10000)
        return () => {
          clearInterval(interval);
        }
    }, [])

    return (
      <>
        <ListAlerts alerts={alerts} />
      </>
    )
}
like image 102
azundo Avatar answered Oct 25 '22 13:10

azundo


I found this blog by Dan Abramov which explains the idea of a useInterval hook that solves this problem. You can use it like this :

function Counter() {
  useInterval(() => {
    callMyApi()
  }, 1000);
}

And declare the useInterval hook this way :

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

function useInterval(callback, delay) {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

Hope it helps someone!

like image 23
Salman Avatar answered Oct 25 '22 13:10

Salman