Ben Borgers

How to poll for fresh data on an interval with Remix

December 30, 2021

Letā€™s say you want to refresh the data on a page of your Remix app on a defined interval, like every 30 seconds.

One solution would be to use the navigate function to ā€œnavigateā€ to the current page, like this: navigate(".", { replace: true }). However, this also causes the pageā€™s scroll position to jump to the top, which often isnā€™t what we want.

Instead, we can use Remixā€™s useFetcher hook.

Hereā€™s the code, and Iā€™ll explain it afterwards:

// app/routes/my-page.jsx

import { useState, useEffect } from "react";
import { useLoaderData, useFetcher } from "remix";

export default function () {
  const loaderData = useLoaderData();
  const [data, setData] = useState(loaderData);

  // Whenever the loader gives us new data
  // (for example, after a form submission),
  // update our `data` state.
  useEffect(() => setData(loaderData), [loaderData]);

  const fetcher = useFetcher();

  // Get fresh data every 30 seconds.
  useEffect(() => {
    const interval = setInterval(() => {
      if (document.visibilityState === "visible") {
        fetcher.load("/my-page");
      }
    }, 30 * 1000);

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

  // When the fetcher comes back with new data,
  // update our `data` state.
  useEffect(() => {
    if (fetcher.data) {
      setData(fetcher.data);
    }
  }, [fetcher.data]);

  return (
    // Construct JSX view here, using `data`.
  );
}
  • Instead of using the loader data directly from useLoaderData, weā€™re using a piece of state called data and keeping data up-to-date with the loader data. This way, we can also update data when we fetch new data every 30 seconds.
  • When the page mounts, we create an interval that runs every 30 seconds. It checks to see whether the tab is in the foreground (no use in reloading data if the user has the tab open but itā€™s in the background), and then loads the current pageā€™s data using a fetcher.
    • When the user leaves this page, React will execute the function that we return from useEffect. Therefore, weā€™re returning a function that cleans up our interval, so this doesnā€™t keep polling every 30 seconds even after the user goes to another page.
  • When the fetcher finishes loading new data, fetcher.data will be updated. In reaction to this, we use another useEffect to update our data state whenever the fetcher returns new data.
    • This useEffect will run once when the page is mounted as well, when the fetcher hasnā€™t fetched any data for us. Therefore, we need to make sure fetcher.data actually has data before using it.

Thatā€™s it! Now, the data for this route will be re-fetched every 30 seconds (or however often youā€™d like), without refreshing the entire page.

More blog posts

Subscribe to my newsletter for a monthly round-up of new blog posts and projects Iā€™m working on!

Twitter ↗ RSS feed ↗