| 1 | import { useEffect } from "react"; |
| 2 | import { useNavigation, useRevalidator } from "react-router"; |
| 3 | |
| 4 | /** How often a page with something moving asks again. */ |
| 5 | export const REFRESH_MS = 4000; |
| 6 | |
| 7 | /** |
| 8 | * Revalidates the page `everyMs` after it last loaded while `on` and the |
| 9 | * tab is visible. Whatever refreshed it last (this, a live change, a form, |
| 10 | * a navigation) starts the count again, so a page that also follows live |
| 11 | * changes is not refreshed twice in a row. Out of view it waits, and asks |
| 12 | * once when the tab comes back if the time has passed meanwhile. |
| 13 | * |
| 14 | * Never while a navigation is pending: a revalidation then restarts the |
| 15 | * navigation, aborting its loaders, so a click to a page slower than the |
| 16 | * interval would never arrive. Never on top of a revalidation still going. |
| 17 | */ |
| 18 | export function useRefreshWhile(on: boolean, everyMs = REFRESH_MS) { |
| 19 | const revalidator = useRevalidator(); |
| 20 | const idle = useNavigation().state === "idle" && revalidator.state === "idle"; |
| 21 | useEffect(() => { |
| 22 | if (!on || !idle) return; |
| 23 | let due = false; |
| 24 | const ask = () => { |
| 25 | if (document.visibilityState === "visible") revalidator.revalidate(); |
| 26 | else due = true; |
| 27 | }; |
| 28 | const timer = setTimeout(ask, everyMs); |
| 29 | const back = () => { |
| 30 | if (due && document.visibilityState === "visible") ask(); |
| 31 | }; |
| 32 | document.addEventListener("visibilitychange", back); |
| 33 | return () => { |
| 34 | clearTimeout(timer); |
| 35 | document.removeEventListener("visibilitychange", back); |
| 36 | }; |
| 37 | // `revalidator` is a new object whenever its state changes, which `idle` already follows. |
| 38 | // eslint-disable-next-line react-hooks/exhaustive-deps |
| 39 | }, [on, idle, everyMs]); |
| 40 | } |