Skip to content

Instantly share code, notes, and snippets.

@dohomi
Last active November 19, 2025 01:00
Show Gist options
  • Select an option

  • Save dohomi/c014cff60e4bbbf4c659ffdc26704510 to your computer and use it in GitHub Desktop.

Select an option

Save dohomi/c014cff60e4bbbf4c659ffdc26704510 to your computer and use it in GitHub Desktop.
Serwist React hook for handling offlineReacy, hasUpdate and update events
import { defaultCache } from "@serwist/vite/worker";
import type { PrecacheEntry, SerwistGlobalConfig } from "serwist";
import { ExpirationPlugin, NetworkFirst, Serwist } from "serwist";
declare global {
interface WorkerGlobalScope extends SerwistGlobalConfig {
__SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
}
}
declare const self: ServiceWorkerGlobalScope;
const serwist = new Serwist({
precacheEntries: self.__SW_MANIFEST,
skipWaiting: false,
clientsClaim: true,
navigationPreload: true,
precacheOptions: {
cleanupOutdatedCaches: true,
concurrency: 10,
},
fallbacks: {
entries: [
{
matcher: ({ request }) => request.mode === "navigate",
url: "/index.html", // support page-reload while offline
},
],
},
runtimeCaching: defaultCache
});
serwist.addEventListeners();
import type { Serwist, SerwistLifecycleWaitingEvent } from "@serwist/window";
import {
useCallback,
useEffect,
useEffectEvent,
useRef,
useState,
} from "react";
import { getSerwist } from "virtual:serwist";
export function useSerwist() {
const [offlineReady, setOfflineReady] = useState(false);
const [hasUpdate, setHasUpdate] = useState(false);
const serwistRef = useRef<Serwist | null>(null);
const handleInstalled = useEffectEvent(() => {
// SW installed, app is offline-ready
setOfflineReady(true);
});
const handleWaiting = useEffectEvent(
(event: SerwistLifecycleWaitingEvent) => {
// New version waiting, so prompting user
setHasUpdate(true);
}
);
const handleControlling = useEffectEvent(() => {
// New SW is controlling, so hard reload
window.location.reload();
});
const handleUpdateCheck = useEffectEvent(async () => {
try {
const sw = serwistRef.current;
if (!sw?.update) return;
await sw.update();
} catch (err) {
console.warn("Update check failed:", err);
}
});
useEffect(() => {
if (!("serviceWorker" in navigator)) return;
let active = true;
(async () => {
try {
const serwist = await getSerwist();
if (!active || !serwist) return;
serwistRef.current = serwist;
serwist.addEventListener("installed", handleInstalled);
serwist.addEventListener("waiting", handleWaiting);
serwist.addEventListener("controlling", handleControlling);
await serwist.register();
} catch (err) {
console.error("Failed to register SW:", err);
}
})();
return () => {
active = false;
const sw = serwistRef.current;
if (sw) {
sw.removeEventListener("installed", handleInstalled);
sw.removeEventListener("waiting", handleWaiting);
sw.removeEventListener("controlling", handleControlling);
}
};
}, []);
useEffect(() => {
const onFocus = () => handleUpdateCheck();
const onVisible = () => {
if (document.visibilityState === "visible") handleUpdateCheck();
};
window.addEventListener("focus", onFocus);
document.addEventListener("visibilitychange", onVisible);
return () => {
window.removeEventListener("focus", onFocus);
document.removeEventListener("visibilitychange", onVisible);
};
}, []);
const update = useCallback(() => {
const sw = serwistRef.current;
if (!sw) return;
sw.messageSkipWaiting();
setHasUpdate(false);
}, []);
return { offlineReady, hasUpdate, update };
}
@prabuvenkat-gh
Copy link

Hei!

Thanks for the gist! By any chance do you have a React 18 version of this? (I will try to redo this with useEffect in the meantime).

@dohomi
Copy link
Author

dohomi commented Nov 19, 2025

I dont but you can replace all useEffectEvent with useCallback and add the missing variables to the dependency arrays of useEffect

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment