React programmatically recall a hook

Viewed 3565

https://codesandbox.io/s/react-hooks-usefetch-cniul

Please see above url for a very simplified version of my code.

I want to be able to refetch data from an API with my hook, within an interval (basically poll an endpoint for data).

What I want is to be able to just call something like refetch (as I've shown in the code as a comment), which would essentially just call fetchData again and update state with the response accordingly.

What's the best way to go about this? The only way I can think of is to add a checker variable in the hook which would be some sort of uuid (Math.random() maybe), return setChecker as what is refetch and just add checker to the array as 2nd useEffect argument to control rerendering. So whenever you call refetch it calls setChecker which updates the random number (checker) and then the function runs again.

Obviously this sounds "hacky", there must be a nicer way of doing it - any ideas?

4 Answers

If you want to have a constant poll going, I think you can move the setInterval() into the hook like so:

function useFetch() {
  const [data, setDataState] = useState(null);
  const [loading, setLoadingState] = useState(true);
  useEffect(() => {
    function fetchData() {
      setLoadingState(true);
      fetch(url)
        .then(j => j.json())
        .then(data => {
          setDataState(data);
          setLoadingState(false);
        });
    }

    const interval = setInterval(() => {
      fetchData();
    }, 5000);

    fetchData();

    return () => clearInterval(interval);
  }, []);

  return [
    {
      data,
      loading
    }
  ];
}

Remember to include the return () => clearInterval(interval); so the hook is cleaned up correctly.

import React, { useEffect, useState, useCallback } from "react";
import ReactDOM from "react-dom";

const url = "https://api.etilbudsavis.dk/v2/dealerfront?country_id=DK";

function useFetch() {
  const [data, setDataState] = useState(null);
  const [loading, setLoadingState] = useState(true);
  const refetch = useCallback(() => {
    function fetchData() {
      console.log("fetch");
      setLoadingState(true);
      fetch(url)
        .then(j => j.json())
        .then(data => {
          setDataState(data);
          setLoadingState(false);
        });
    }
    fetchData();
  }, []);

  return [
    {
      data,
      loading
    },
    refetch
    // fetchData <- somehow return ability to call fetchData function...
  ];
}

function App() {
  const [
    { data, loading },
    refetch
    // refetch
  ] = useFetch();

  useEffect(() => {
    const id = setInterval(() => {
      // Use the refetch here...
      refetch();
    }, 5000);
    return () => {
      clearInterval(id);
    };
  }, [refetch]);

  if (loading) return <h1>Loading</h1>;
  return (
    <>
      <button onClick={refetch}>Refetch</button>
      <code style={{ display: "block" }}>
        <pre>{JSON.stringify(data[0], null, 2)}</pre>
      </code>
    </>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Maybe the following will work, it needs some adjustments to useFetch but you can still call it normally in other places.

//maybe you can pass url as well so you can use
//  it with other components and urls
function useFetch(refresh) {
  //code removed
  useEffect(() => {
    //code removed
  }, [refresh]);
  //code removed
}

const [refresh, setRefresh] = useState({});
const [{ data, loading }] = useFetch(refresh);
useEffect(() => {
  const interval = setInterval(
    () => setRefresh({}), //forces re render
    5000
  );
  return () => clearInterval(interval); //clean up
});

Simple answer to question:

export default function App() {
  const [entities, setEntities] = useState();
  const [loading, setLoadingState] = useState(true);

  const getEntities = () => {
    setLoadingState(true);

    //Changet the URL with your own
    fetch("http://google.com", {
      method: "GET",
    })
      .then((data) => data.json())
      .then((resp) => {
        setEntities(resp);
        setLoadingState(false);
      });
  };

  useEffect(() => {
    const interval = setInterval(() => {
      getEntities();
    }, 5000);

    return () => clearInterval(interval);
  }, []);
}

Related