React hooks. Periodic run useEffect
Asked Answered
M

2

10

I need periodically fetch data and update it to the screen. I have this code:

const [temperature, setTemperature] = useState();

useEffect(() => {

fetch("urlToWeatherData")
  .then(function(response) {
     if (response.status !== 200) {
      console.log(
        "Looks like there was a problem. Status Code: " + response.status
      );
      return;
    response.json().then(function(data) {
     console.log(data[0].temperature);
     setTemperature(data[0].temperature);

    });
  })
  .catch(function(err) {
    console.log("Fetch Error :-S", err);
  });
 }, []  );

So, is there any neat way to run it every 15 seconds, in example? Thanks!

Monster answered 9/1, 2020 at 15:29 Comment(0)
M
20

Wrap it in an interval, and don't forget to return a teardown function to cancel the interval when the component unmounts:

useEffect(() => {
  const id = setInterval(() => 
    fetch("urlToWeatherData")
      .then(function(response) {
         if (response.status !== 200) {
          console.log(
            "Looks like there was a problem. Status Code: " + response.status
          );
          return;
        response.json().then(function(data) {
         console.log(data[0].temperature);
         setTemperature(data[0].temperature);
        });
      })
      .catch(function(err) {
        console.log("Fetch Error :-S", err);
      });
  ), 15000);

  return () => clearInterval(id);  
}, []);
Medorra answered 9/1, 2020 at 15:31 Comment(2)
Just an added note, if you want to run it immediately rather than 15 seconds after initialization, just name the arrow function, pass the reference to setInterval() and then call it immediately before returning the teardown.Rosel
Thank you all! I did wrap it in a function and then just called twice, like: getData(); setInterval(getData, 15000); Now it seems to work exactly as it should.Monster
R
9

Just to give a different approach, you can define a custom hook for extracting this functionality into a reusable function:

const useInterval = (callback, interval, immediate) => {
  const ref = useRef();

  // keep reference to callback without restarting the interval
  useEffect(() => {
    ref.current = callback;
  }, [callback]);

  useEffect(() => {
    // when this flag is set, closure is stale
    let cancelled = false;

    // wrap callback to pass isCancelled getter as an argument
    const fn = () => {
      ref.current(() => cancelled);
    };

    // set interval and run immediately if requested
    const id = setInterval(fn, interval);
    if (immediate) fn();

    // define cleanup logic that runs
    // when component is unmounting
    // or when or interval or immediate have changed
    return () => {
      cancelled = true;
      clearInterval(id);
    };
  }, [interval, immediate]);
};

Then you can use the hook like this:

const [temperature, setTemperature] = useState();

useInterval(async (isCancelled) => {
  try {
    const response = await fetch('urlToWeatherData');
    // check for cancellation after each await
    // to prevent further action on a stale closure
    if (isCancelled()) return;

    if (response.status !== 200) {
      // throw here to handle errors in catch block
      throw new Error(response.statusText);
    }

    const [{ temperature }] = await response.json();
    if (isCancelled()) return;

    console.log(temperature);
    setTemperature(temperature);
  } catch (err) {
    console.log('Fetch Error:', err);
  }
}, 15000, true);

We can prevent the callback from calling setTemperature() if the component is unmounted by checking isCancelled(). For more general use-cases of useInterval() when the callback is dependent on stateful variables, you should prefer useReducer() or at least use the functional update form of useState().

Rosel answered 9/1, 2020 at 17:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.