Data Revalidation in Remix
Tags: remix, webdev, typescript, react
Published: 2023-02-20 18:53:35

Libraries like [SWR](https://swr.vercel.app/docs/getting-started) or [React Query](https://tanstack.com/query/v4) have automatic data revalidation built in, but what about [Remix](https://remix.run)?

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**
```ts
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 });
};
```

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**
```ts
export function User() {
    const { user } = useLoaderData<LoaderData>();

    return <p>{user.username}</p>;
}
```

**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.

```ts
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]
  );
};
```

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

**Create a hook to revalidate on focus**
```ts
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]
  );
};
```

**Create a hook to revalidate based on interval**

```ts
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]
  );
};
```

**Use the hooks we created**

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

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

    return <p>{user.username}</p>;
}
```

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