UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

126 lines (114 loc) 3.33 kB
import { useSyncExternalStore } from 'react' import { useServerContext } from './vite/one-server-only' import type { One } from './vite/types' /** * Re-export for convenience */ export type RouteMatch = One.RouteMatch // client-side matches store for navigation updates let clientMatches: RouteMatch[] = [] const clientMatchesListeners = new Set<() => void>() function subscribeToClientMatches(callback: () => void) { clientMatchesListeners.add(callback) return () => clientMatchesListeners.delete(callback) } function getClientMatchesSnapshot() { return clientMatches } /** * Update the client-side matches store. * Called after navigation to update the matches with new loader data. * @internal */ export function setClientMatches(matches: RouteMatch[]) { clientMatches = matches for (const listener of clientMatchesListeners) { listener() } } /** * Returns an array of all matched routes from root to the current page. * Each match contains the route's loader data, params, and route ID. * * On the server (SSR), this returns the matches computed during the request. * On the client, this returns cached matches from hydration or the last navigation. * * @example * ```tsx * // In a layout component * function DocsLayout({ children }) { * const matches = useMatches() * const pageMatch = matches[matches.length - 1] * const headings = pageMatch?.loaderData?.headings * * return ( * <div> * <TableOfContents headings={headings} /> * {children} * </div> * ) * } * ``` * * @example * ```tsx * // Building breadcrumbs * function Breadcrumbs() { * const matches = useMatches() * * return ( * <nav> * {matches.map((match) => ( * <a key={match.routeId} href={match.pathname}> * {match.loaderData?.title ?? match.routeId} * </a> * ))} * </nav> * ) * } * ``` */ export function useMatches(): RouteMatch[] { const serverContext = useServerContext() // on server, return from context directly if (process.env.VITE_ENVIRONMENT === 'ssr') { return serverContext?.matches ?? [] } // on client, use sync external store for reactivity // the server snapshot (3rd arg) is used during hydration to match SSR output const clientStoreMatches = useSyncExternalStore( subscribeToClientMatches, getClientMatchesSnapshot, // server snapshot for hydration - must match what SSR rendered () => serverContext?.matches ?? [] ) // always return client store on client - it's initialized from server context // during hydration via initClientMatches, then updated on navigation return clientStoreMatches } /** * Find a specific match by route ID. * * @example * ```tsx * const docsMatch = useMatch('docs/_layout') * const navItems = docsMatch?.loaderData?.navItems * ``` */ export function useMatch(routeId: string): RouteMatch | undefined { const matches = useMatches() return matches.find((m) => m.routeId === routeId) } /** * Get the current page's match (the last/deepest match). * * @example * ```tsx * const pageMatch = usePageMatch() * const { title, description } = pageMatch?.loaderData ?? {} * ``` */ export function usePageMatch(): RouteMatch | undefined { const matches = useMatches() return matches[matches.length - 1] }