Last active
November 19, 2025 01:00
-
-
Save dohomi/c014cff60e4bbbf4c659ffdc26704510 to your computer and use it in GitHub Desktop.
Serwist React hook for handling offlineReacy, hasUpdate and update events
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }; | |
| } |
Author
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
Hei!
Thanks for the gist! By any chance do you have a React 18 version of this? (I will try to redo this with
useEffectin the meantime).