DEV Community

Vansh Wadhwa
Vansh Wadhwa

Posted on

Data Revalidation in Remix

Libraries like SWR or React Query have automatic data revalidation built in, but what about Remix?

Remix has a great built-in utility to fetch data (loader and useLoaderData). It automatically revalidates data on all actions.

Let’s see how you can add more ways to revalidate your data such as interval/focus based data revalidation:

Create a loader to fetch your data

type LoaderData= {
    user: User;
};

export const loader: LoaderFunction = async () => {
    const res = await fetch("/api/user/me");
    const data = await res.json();

    return json<LoaderData>({ user: data });
};
Enter fullscreen mode Exit fullscreen mode

This will make remix fetch the data whenever the route loads.

Make sure loader is exported from the same file your route is in.

Use the loader in your component

export function User() {
    const { user } = useLoaderData<LoaderData>();

    return <p>{user.username}</p>;
}
Enter fullscreen mode Exit fullscreen mode

Create a revalidate hook

useRevalidate can be use to trigger data refresh. It triggers a navigation event to the current route which triggers Remix to call loader of the route.

import { useCallback } from "react";
import { useNavigate } from "@remix-run/react";

export const useRevalidate = () => {
  // We get the navigate function from React Rotuer
  let navigate = useNavigate();
  // And return a function which will navigate to `.` (same URL) and replace it
  return useCallback(
    function revalidate() {
      navigate(".", { replace: true });
    },
    [navigate]
  );
};
Enter fullscreen mode Exit fullscreen mode

replace: true prevents it from creating duplicate routes in browser history stack.

Create a hook to revalidate on focus

import { useEffect } from "react";

export const useRevalidateOnFocus = ({
  enabled = false,
}: {
  enabled?: boolean;
}) => {
  let revalidate = useRevalidate();

  useEffect(
    function revalidateOnFocus() {
      if (!enabled) return;
      function onFocus() {
        revalidate();
      }
      window.addEventListener("focus", onFocus);
      return () => window.removeEventListener("focus", onFocus);
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [revalidate]
  );

  useEffect(
    function revalidateOnVisibilityChange() {
      if (!enabled) return;
      function onVisibilityChange() {
        revalidate();
      }
      window.addEventListener("visibilitychange", onVisibilityChange);
      return () =>
        window.removeEventListener("visibilitychange", onVisibilityChange);
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [revalidate]
  );
};
Enter fullscreen mode Exit fullscreen mode

Create a hook to revalidate based on interval

import { useEffect } from "react";

export const useRevalidateOnInterval = ({
  enabled = false,
  interval = 1000,
}: {
  enabled?: boolean;
  interval?: number;
}) => {
  let revalidate = useRevalidate();
  useEffect(
    function revalidateOnInterval() {
      if (!enabled) return;
      let intervalId = setInterval(revalidate, interval);
      return () => clearInterval(intervalId);
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [revalidate]
  );
};
Enter fullscreen mode Exit fullscreen mode

Use the hooks we created

Now just call the hook in route where loader is in.

export function User() {
    useRevalidateOnInterval({
      enabled: true,
      interval: 5 * 1000,
    });
    const { user } = useLoaderData<LoaderData>();

    return <p>{user.username}</p>;
}
Enter fullscreen mode Exit fullscreen mode

in the above example it would re-validate user every 5 seconds.

Latest comments (0)