| 1 | import { useEffect } from "react"; |
| 2 | import { type Location, createPath, useNavigation } from "react-router"; |
| 3 | |
| 4 | /** |
| 5 | * How long a client navigation may load before the page gives up on it and |
| 6 | * loads the address as a whole page. Longer than any read may take on the |
| 7 | * server (lib/deadline.ts `SLOW_READ_DEADLINE_MS`): a slow page that would |
| 8 | * arrive is never cut off and started again, and a stuck service shows its |
| 9 | * error page first. This catches what the server cannot: a request lost on |
| 10 | * the way, a connection that went quiet, a route's code that never arrived. |
| 11 | */ |
| 12 | export const STUCK_NAVIGATION_MS = 75_000; |
| 13 | |
| 14 | /** |
| 15 | * Where to send a navigation that has been loading too long, or null when |
| 16 | * there is nothing safe to retry. Only "loading" is retried: it reads, even |
| 17 | * after a form was posted (the action already answered and the page is |
| 18 | * reading what it changed). "submitting" is a write still on its way, and |
| 19 | * sending it again could do it twice. |
| 20 | */ |
| 21 | export function stuckTarget(navigation: { state: string; location?: Pick<Location, "pathname" | "search" | "hash"> | null }): string | null { |
| 22 | if (navigation.state !== "loading" || !navigation.location) return null; |
| 23 | return createPath(navigation.location); |
| 24 | } |
| 25 | |
| 26 | /** |
| 27 | * Never leaves the page on its loading bar: a navigation still loading |
| 28 | * after `STUCK_NAVIGATION_MS` is finished as a document load of the same |
| 29 | * address, which either arrives or shows the server's error page. |
| 30 | */ |
| 31 | export function useStuckNavigationRescue(ms = STUCK_NAVIGATION_MS) { |
| 32 | const target = stuckTarget(useNavigation()); |
| 33 | useEffect(() => { |
| 34 | if (!target) return; |
| 35 | const timer = window.setTimeout(() => { |
| 36 | console.warn(`g1t: navigation to ${target} still loading after ${ms}ms; loading it as a whole page`); |
| 37 | window.location.assign(target); |
| 38 | }, ms); |
| 39 | return () => window.clearTimeout(timer); |
| 40 | }, [target, ms]); |
| 41 | } |