diff --git a/web/src/components/layout/Layout.tsx b/web/src/components/layout/Layout.tsx index e9a8b48..1953e01 100644 --- a/web/src/components/layout/Layout.tsx +++ b/web/src/components/layout/Layout.tsx @@ -4,9 +4,11 @@ import MobileBackButton from "./MobileBackButton"; import Footer from "./Footer"; import LoginModal from "../LoginModal"; import { LoginModalProvider } from "../../lib/loginModal"; +import { useRevalidateOnFocus } from "../../hooks/useRevalidateOnFocus"; export default function Layout() { const isLoading = useNavigation().state === "loading"; + useRevalidateOnFocus(); return ( diff --git a/web/src/hooks/useRevalidateOnFocus.ts b/web/src/hooks/useRevalidateOnFocus.ts new file mode 100644 index 0000000..f7fcf27 --- /dev/null +++ b/web/src/hooks/useRevalidateOnFocus.ts @@ -0,0 +1,32 @@ +/** Revalidate active route loaders when the tab regains focus/visibility or + * the browser reconnects. Throttled so rapid focus changes don't storm. */ + +import { useEffect, useRef } from "react"; +import { useRevalidator } from "react-router-dom"; + +const MIN_INTERVAL_MS = 30_000; + +export function useRevalidateOnFocus() { + const revalidator = useRevalidator(); + const lastAt = useRef(0); + + useEffect(() => { + function maybeRevalidate() { + if (document.hidden) return; + if (revalidator.state !== "idle") return; + const now = Date.now(); + if (now - lastAt.current < MIN_INTERVAL_MS) return; + lastAt.current = now; + revalidator.revalidate(); + } + + window.addEventListener("focus", maybeRevalidate); + window.addEventListener("online", maybeRevalidate); + document.addEventListener("visibilitychange", maybeRevalidate); + return () => { + window.removeEventListener("focus", maybeRevalidate); + window.removeEventListener("online", maybeRevalidate); + document.removeEventListener("visibilitychange", maybeRevalidate); + }; + }, [revalidator]); +}