import { useCallback, useEffect, useRef, useState } from 'react'; /** * Path routing over the History API. The server serves this document for * every account path, so a reload or a shared link lands on the same screen * the nav does. Both the pathname and the query string are tracked, so a * detail view like a collection is a real, addressable page. */ export function useRouter() { const [href, setHref] = useState( () => window.location.pathname + window.location.search, ); // Depth in this app's own history stack. 0 is the first page loaded, so Back // can tell a real step back (still inside the app) from one that would leave. const depth = useRef(window.history.state?.idx ?? 0); useEffect(() => { if (window.history.state?.idx == null) { window.history.replaceState({ idx: 0 }, ''); } /** @param {PopStateEvent} event */ const onPop = (event) => { depth.current = event.state?.idx ?? 0; setHref(window.location.pathname + window.location.search); }; window.addEventListener('popstate', onPop); return () => window.removeEventListener('popstate', onPop); }, []); const navigate = useCallback( /** @param {string} next */ (next) => { depth.current += 1; window.history.pushState({ idx: depth.current }, '', next); setHref(next); window.scrollTo(0, 0); }, [], ); // Step back to wherever the visitor came from. When they landed here directly // (a shared link or a reload, nothing to step back to), take the fallback so // Back still lands somewhere sensible instead of leaving the app. const back = useCallback( /** @param {string} [fallback] */ (fallback) => { if (depth.current > 0) window.history.back(); else if (fallback) navigate(fallback); }, [navigate], ); const url = new URL(href, window.location.origin); return { path: url.pathname, params: url.searchParams, navigate, back }; }