UNPKG

51.8 kB JavaScript View Raw
1/**
2 * react-router v8.5.1
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { createPath, invariant, parsePath, warning } from "./router/history.js";
12import { convertRouteMatchToUiMatch, decodePath, getResolveToMatches, getRoutePattern, isBrowser, isRouteErrorResponse, joinPaths, matchPath, matchRoutes, parseToInfo, resolveTo, stripBasename } from "./router/utils.js";
13import { getNavigatorCurrentUrl, validateNavigationTarget } from "./router/navigation.js";
14import { IDLE_BLOCKER, hasInvalidProtocol } from "./router/router.js";
15import { AwaitContext, DataRouterContext, DataRouterDataContext, DataRouterNavigationContext, DataRouterStateContext, FetchersContext, IsDataRouteContext, LocationContext, NavigationContext, RSCRouterContext, RouteContext, RouteErrorContext, RouteIdContext } from "./context.js";
16import { decodeRedirectErrorDigest, decodeRouteErrorResponseDigest } from "./errors.js";
17import * as React$1 from "react";
18//#region lib/hooks.tsx
19/**
20* Resolves a URL against the current {@link Location}.
21*
22* @example
23* import { useHref } from "react-router";
24*
25* function SomeComponent() {
26* let href = useHref("some/where");
27* // "/resolved/some/where"
28* }
29*
30* @public
31* @category Hooks
32* @param to The path to resolve
33* @param options Options
34* @param options.relative Defaults to `"route"` so routing is relative to the
35* route tree.
36* Set to `"path"` to make relative routing operate against path segments.
37* @returns The resolved href string
38*/
39function useHref(to, { relative } = {}) {
40 invariant(useInRouterContext(), `useHref() may be used only in the context of a <Router> component.`);
41 let { basename, navigator } = React$1.useContext(NavigationContext);
42 let { hash, pathname, search } = useResolvedPath(to, { relative });
43 let joinedPathname = pathname;
44 if (basename !== "/") joinedPathname = pathname === "/" ? basename : joinPaths([basename, pathname]);
45 return navigator.createHref({
46 pathname: joinedPathname,
47 search,
48 hash
49 });
50}
51/**
52* Returns `true` if this component is a descendant of a {@link Router}, useful
53* to ensure a component is used within a {@link Router}.
54*
55* @public
56* @category Hooks
57* @mode framework
58* @mode data
59* @returns Whether the component is within a {@link Router} context
60*/
61function useInRouterContext() {
62 return React$1.useContext(LocationContext) != null;
63}
64/**
65* Returns the current {@link Location}. This can be useful if you'd like to
66* perform some side effect whenever it changes.
67*
68* @example
69* import * as React from 'react'
70* import { useLocation } from 'react-router'
71*
72* function SomeComponent() {
73* let location = useLocation()
74*
75* React.useEffect(() => {
76* // Google Analytics
77* ga('send', 'pageview')
78* }, [location]);
79*
80* return (
81* // ...
82* );
83* }
84*
85* @public
86* @category Hooks
87* @returns The current {@link Location} object
88*/
89function useLocation() {
90 invariant(useInRouterContext(), `useLocation() may be used only in the context of a <Router> component.`);
91 return React$1.useContext(LocationContext).location;
92}
93/**
94* Returns the current {@link Navigation} action which describes how the router
95* came to the current {@link Location}, either by a pop, push, or replace on
96* the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) stack.
97*
98* @public
99* @category Hooks
100* @returns The current {@link NavigationType} (`"POP"`, `"PUSH"`, or `"REPLACE"`)
101*/
102function useNavigationType() {
103 return React$1.useContext(LocationContext).navigationType;
104}
105/**
106* Returns a {@link PathMatch} object if the given pattern matches the current URL.
107* This is useful for components that need to know "active" state, e.g.
108* {@link NavLink | `<NavLink>`}.
109*
110* @public
111* @category Hooks
112* @param pattern The pattern to match against the current {@link Location}
113* @returns The path match object if the pattern matches, `null` otherwise
114*/
115function useMatch(pattern) {
116 invariant(useInRouterContext(), `useMatch() may be used only in the context of a <Router> component.`);
117 let { pathname } = useLocation();
118 return React$1.useMemo(() => matchPath(pattern, decodePath(pathname)), [pathname, pattern]);
119}
120const navigateEffectWarning = "You should call navigate() in a React.useEffect(), not when your component is first rendered.";
121/**
122* Returns a function that lets you navigate programmatically in the browser in
123* response to user interactions or effects.
124*
125* It's often better to use {@link redirect} in [`action`](../../start/framework/route-module#action)/[`loader`](../../start/framework/route-module#loader)
126* functions than this hook.
127*
128* The returned function signature is `navigate(to, options?)`/`navigate(delta)` where:
129*
130* * `to` can be a string path, a {@link To} object, or a number (delta)
131* * `options` contains options for modifying the navigation
132* * These options work in all modes (Framework, Data, and Declarative):
133* * `relative`: `"route"` or `"path"` to control relative routing logic
134* * `replace`: Replace the current entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History) stack
135* * `state`: Optional [`history.state`](https://developer.mozilla.org/en-US/docs/Web/API/History/state) to include with the new {@link Location}
136* * These options only work in Framework and Data modes:
137* * `flushSync`: Wrap the DOM updates in [`ReactDom.flushSync`](https://react.dev/reference/react-dom/flushSync)
138* * `preventScrollReset`: Do not scroll back to the top of the page after navigation
139* * `viewTransition`: **Deprecated.** Use React's [`<ViewTransition>`](https://react.dev/reference/react/ViewTransition) component instead. See the [migration guide](../../how-to/view-transitions).
140*
141* @example
142* import { useNavigate } from "react-router";
143*
144* function SomeComponent() {
145* let navigate = useNavigate();
146* return (
147* <button onClick={() => navigate(-1)}>
148* Go Back
149* </button>
150* );
151* }
152*
153* @additionalExamples
154* ### Navigate to another path
155*
156* ```tsx
157* navigate("/some/route");
158* navigate("/some/route?search=param");
159* ```
160*
161* ### Navigate with a {@link To} object
162*
163* All properties are optional.
164*
165* ```tsx
166* navigate(
167* {
168* pathname: "/some/route",
169* search: "?search=param",
170* hash: "#hash",
171* },
172* {
173* state: { some: "state" },
174* },
175* );
176* ```
177*
178* If you use `state`, that will be available on the {@link Location} object on
179* the next page. Access it with `useLocation().state` (see {@link useLocation}).
180*
181* ### Navigate back or forward in the history stack
182*
183* ```tsx
184* // back
185* // often used to close modals
186* navigate(-1);
187*
188* // forward
189* // often used in a multistep wizard workflows
190* navigate(1);
191* ```
192*
193* Be cautious with `navigate(number)`. If your application can load up to a
194* route that has a button that tries to navigate forward/back, there may not be
195* a [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
196* entry to go back or forward to, or it can go somewhere you don't expect
197* (like a different domain).
198*
199* Only use this if you're sure they will have an entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
200* stack to navigate to.
201*
202* ### Replace the current entry in the history stack
203*
204* This will remove the current entry in the [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
205* stack, replacing it with a new one, similar to a server side redirect.
206*
207* ```tsx
208* navigate("/some/route", { replace: true });
209* ```
210*
211* ### Prevent Scroll Reset
212*
213* [MODES: framework, data]
214*
215* To prevent {@link ScrollRestoration | `<ScrollRestoration>`} from resetting
216* the scroll position, use the `preventScrollReset` option.
217*
218* ```tsx
219* navigate("?some-tab=1", { preventScrollReset: true });
220* ```
221*
222* For example, if you have a tab interface connected to search params in the
223* middle of a page, and you don't want it to scroll to the top when a tab is
224* clicked.
225*
226* ### Return Type Augmentation
227*
228* Internally, `useNavigate` uses a separate implementation when you are in
229* Declarative mode versus Data/Framework mode - the primary difference being
230* that the latter is able to return a stable reference that does not change
231* identity across navigations. The implementation in Data/Framework mode also
232* returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
233* that resolves when the navigation is completed. This means the return type of
234* `useNavigate` is `void | Promise<void>`. This is accurate, but can lead to
235* some red squigglies based on the union in the return value:
236*
237* - If you're using `typescript-eslint`, you may see errors from
238* [`@typescript-eslint/no-floating-promises`](https://typescript-eslint.io/rules/no-floating-promises)
239* - In Framework/Data mode, `React.use(navigate())` will show a false-positive
240* `Argument of type 'void | Promise<void>' is not assignable to parameter of
241* type 'Usable<void>'` error
242*
243* The easiest way to work around these issues is to augment the type based on the
244* router you're using:
245*
246* ```ts
247* // If using <BrowserRouter>
248* declare module "react-router" {
249* interface NavigateFunction {
250* (to: To, options?: NavigateOptions): void;
251* (delta: number): void;
252* }
253* }
254*
255* // If using <RouterProvider> or Framework mode
256* declare module "react-router" {
257* interface NavigateFunction {
258* (to: To, options?: NavigateOptions): Promise<void>;
259* (delta: number): Promise<void>;
260* }
261* }
262* ```
263*
264* @public
265* @category Hooks
266* @returns A navigate function for programmatic navigation
267*/
268function useNavigate() {
269 return React$1.useContext(IsDataRouteContext) ? useNavigateStable() : useNavigateUnstable();
270}
271function useNavigateUnstable() {
272 invariant(useInRouterContext(), `useNavigate() may be used only in the context of a <Router> component.`);
273 let dataRouterContext = React$1.useContext(DataRouterContext);
274 let { basename, navigator } = React$1.useContext(NavigationContext);
275 let { matches } = React$1.useContext(RouteContext);
276 let { pathname: locationPathname } = useLocation();
277 let routePathnamesJson = JSON.stringify(getResolveToMatches(matches));
278 let activeRef = React$1.useRef(false);
279 React$1.useLayoutEffect(() => {
280 activeRef.current = true;
281 });
282 return React$1.useCallback((to, options = {}) => {
283 warning(activeRef.current, navigateEffectWarning);
284 if (!activeRef.current) return;
285 if (typeof to === "number") {
286 navigator.go(to);
287 return;
288 }
289 let path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, options.relative === "path");
290 if (dataRouterContext == null && basename !== "/") path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
291 validateNavigationTarget(typeof to === "string" ? to : createPath(to), navigator.createHref(path), getNavigatorCurrentUrl(navigator), "reject");
292 (!!options.replace ? navigator.replace : navigator.push)(path, options.state, options);
293 }, [
294 basename,
295 navigator,
296 routePathnamesJson,
297 locationPathname,
298 dataRouterContext
299 ]);
300}
301const OutletContext = React$1.createContext(null);
302/**
303* Returns the parent route {@link Outlet | `<Outlet context>`}.
304*
305* Often parent routes manage state or other values you want shared with child
306* routes. You can create your own [context provider](https://react.dev/learn/passing-data-deeply-with-context)
307* if you like, but this is such a common situation that it's built-into
308* {@link Outlet | `<Outlet>`}.
309*
310* ```tsx
311* // Parent route
312* function Parent() {
313* const [count, setCount] = React.useState(0);
314* return <Outlet context={[count, setCount]} />;
315* }
316* ```
317*
318* ```tsx
319* // Child route
320* import { useOutletContext } from "react-router";
321*
322* function Child() {
323* const [count, setCount] = useOutletContext();
324* const increment = () => setCount((c) => c + 1);
325* return <button onClick={increment}>{count}</button>;
326* }
327* ```
328*
329* If you're using TypeScript, we recommend the parent component provide a
330* custom hook for accessing the context value. This makes it easier for
331* consumers to get nice typings, control consumers, and know who's consuming
332* the context value.
333*
334* Here's a more realistic example:
335*
336* ```tsx filename=src/routes/dashboard.tsx lines=[14,20]
337* import { useState } from "react";
338* import { Outlet, useOutletContext } from "react-router";
339*
340* import type { User } from "./types";
341*
342* type ContextType = { user: User | null };
343*
344* export default function Dashboard() {
345* const [user, setUser] = useState<User | null>(null);
346*
347* return (
348* <div>
349* <h1>Dashboard</h1>
350* <Outlet context={{ user } satisfies ContextType} />
351* </div>
352* );
353* }
354*
355* export function useUser() {
356* return useOutletContext<ContextType>();
357* }
358* ```
359*
360* ```tsx filename=src/routes/dashboard/messages.tsx lines=[1,4]
361* import { useUser } from "../dashboard";
362*
363* export default function DashboardMessages() {
364* const { user } = useUser();
365* return (
366* <div>
367* <h2>Messages</h2>
368* <p>Hello, {user.name}!</p>
369* </div>
370* );
371* }
372* ```
373*
374* @public
375* @category Hooks
376* @returns The context value passed to the parent {@link Outlet} component
377*/
378function useOutletContext() {
379 return React$1.useContext(OutletContext);
380}
381/**
382* Returns the element for the child route at this level of the route
383* hierarchy. Used internally by {@link Outlet | `<Outlet>`} to render child
384* routes.
385*
386* @public
387* @category Hooks
388* @param context The context to pass to the outlet
389* @returns The child route element or `null` if no child routes match
390*/
391function useOutlet(context) {
392 let outlet = React$1.useContext(RouteContext).outlet;
393 return React$1.useMemo(() => outlet && /* @__PURE__ */ React$1.createElement(OutletContext.Provider, { value: context }, outlet), [outlet, context]);
394}
395/**
396* Returns an object of key/value-pairs of the dynamic params from the current
397* URL that were matched by the routes. Child routes inherit all params from
398* their parent routes.
399*
400* Assuming a route pattern like `/posts/:postId` is matched by `/posts/123`
401* then `params.postId` will be `"123"`.
402*
403* @example
404* import { useParams } from "react-router";
405*
406* function SomeComponent() {
407* let params = useParams();
408* params.postId;
409* }
410*
411* @additionalExamples
412* ### Basic Usage
413*
414* ```tsx
415* import { useParams } from "react-router";
416*
417* // given a route like:
418* <Route path="/posts/:postId" element={<Post />} />;
419*
420* // or a data route like:
421* createBrowserRouter([
422* {
423* path: "/posts/:postId",
424* component: Post,
425* },
426* ]);
427*
428* // or in routes.ts
429* route("/posts/:postId", "routes/post.tsx");
430* ```
431*
432* Access the params in a component:
433*
434* ```tsx
435* import { useParams } from "react-router";
436*
437* export default function Post() {
438* let params = useParams();
439* return <h1>Post: {params.postId}</h1>;
440* }
441* ```
442*
443* ### Multiple Params
444*
445* Patterns can have multiple params:
446*
447* ```tsx
448* "/posts/:postId/comments/:commentId";
449* ```
450*
451* All will be available in the params object:
452*
453* ```tsx
454* import { useParams } from "react-router";
455*
456* export default function Post() {
457* let params = useParams();
458* return (
459* <h1>
460* Post: {params.postId}, Comment: {params.commentId}
461* </h1>
462* );
463* }
464* ```
465*
466* ### Catchall Params
467*
468* Catchall params are defined with `*`:
469*
470* ```tsx
471* "/files/*";
472* ```
473*
474* The matched value will be available in the params object as follows:
475*
476* ```tsx
477* import { useParams } from "react-router";
478*
479* export default function File() {
480* let params = useParams();
481* let catchall = params["*"];
482* // ...
483* }
484* ```
485*
486* You can destructure the catchall param:
487*
488* ```tsx
489* export default function File() {
490* let { "*": catchall } = useParams();
491* console.log(catchall);
492* }
493* ```
494*
495* @public
496* @category Hooks
497* @returns An object containing the dynamic route parameters
498*/
499function useParams() {
500 let { matches } = React$1.useContext(RouteContext);
501 return matches[matches.length - 1]?.params ?? {};
502}
503/**
504* Resolves the pathname of the given `to` value against the current
505* {@link Location}. Similar to {@link useHref}, but returns a
506* {@link Path} instead of a string.
507*
508* @example
509* import { useResolvedPath } from "react-router";
510*
511* function SomeComponent() {
512* // if the user is at /dashboard/profile
513* let path = useResolvedPath("../accounts");
514* path.pathname; // "/dashboard/accounts"
515* path.search; // ""
516* path.hash; // ""
517* }
518*
519* @public
520* @category Hooks
521* @param to The path to resolve
522* @param options Options
523* @param options.relative Defaults to `"route"` so routing is relative to the route tree.
524* Set to `"path"` to make relative routing operate against path segments.
525* @returns The resolved {@link Path} object with `pathname`, `search`, and `hash`
526*/
527function useResolvedPath(to, { relative } = {}) {
528 let { matches } = React$1.useContext(RouteContext);
529 let { pathname: locationPathname } = useLocation();
530 let routePathnamesJson = JSON.stringify(getResolveToMatches(matches));
531 return React$1.useMemo(() => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, relative === "path"), [
532 to,
533 routePathnamesJson,
534 locationPathname,
535 relative
536 ]);
537}
538/**
539* Hook version of {@link Routes | `<Routes>`} that uses objects instead of
540* components. These objects have the same properties as the component props.
541* The return value of `useRoutes` is either a valid React element you can use
542* to render the route tree, or `null` if nothing matched.
543*
544* @example
545* import { useRoutes } from "react-router";
546*
547* function App() {
548* let element = useRoutes([
549* {
550* path: "/",
551* element: <Dashboard />,
552* children: [
553* {
554* path: "messages",
555* element: <DashboardMessages />,
556* },
557* { path: "tasks", element: <DashboardTasks /> },
558* ],
559* },
560* { path: "team", element: <AboutPage /> },
561* ]);
562*
563* return element;
564* }
565*
566* @public
567* @category Hooks
568* @param routes An array of {@link RouteObject}s that define the route hierarchy
569* @param locationArg An optional {@link Location} object or pathname string to
570* use instead of the current {@link Location}
571* @returns A React element to render the matched route, or `null` if no routes matched
572*/
573function useRoutes(routes, locationArg) {
574 return useRoutesImpl(routes, locationArg);
575}
576function useRoutesImpl(routes, locationArg, dataRouterOpts) {
577 invariant(useInRouterContext(), `useRoutes() may be used only in the context of a <Router> component.`);
578 let { navigator } = React$1.useContext(NavigationContext);
579 let { matches: parentMatches } = React$1.useContext(RouteContext);
580 let routeMatch = parentMatches[parentMatches.length - 1];
581 let parentParams = routeMatch ? routeMatch.params : {};
582 let parentPathname = routeMatch ? routeMatch.pathname : "/";
583 let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/";
584 let parentRoute = routeMatch && routeMatch.route;
585 {
586 let parentPath = parentRoute && parentRoute.path || "";
587 warningOnce(parentPathname, !parentRoute || parentPath.endsWith("*") || parentPath.endsWith("*?"), `You rendered descendant <Routes> (or called \`useRoutes()\`) at "${parentPathname}" (under <Route path="${parentPath}">) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render.\n\nPlease change the parent <Route path="${parentPath}"> to <Route path="${parentPath === "/" ? "*" : `${parentPath}/*`}\">.`);
588 }
589 let locationFromContext = useLocation();
590 let location;
591 if (locationArg) {
592 let parsedLocationArg = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
593 invariant(parentPathnameBase === "/" || parsedLocationArg.pathname?.startsWith(parentPathnameBase), `When overriding the location using \`<Routes location>\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${parentPathnameBase}" but pathname "${parsedLocationArg.pathname}" was given in the \`location\` prop.`);
594 location = parsedLocationArg;
595 } else location = locationFromContext;
596 let pathname = location.pathname || "/";
597 let remainingPathname = pathname;
598 if (parentPathnameBase !== "/") {
599 let parentSegments = parentPathnameBase.replace(/^\//, "").split("/");
600 remainingPathname = "/" + pathname.replace(/^\//, "").split("/").slice(parentSegments.length).join("/");
601 }
602 let matches;
603 if (dataRouterOpts) if (dataRouterOpts.state.matches.length) matches = dataRouterOpts.state.matches.map((m) => Object.assign(m, { route: dataRouterOpts.manifest[m.route.id] || m.route }));
604 else matches = dataRouterOpts.router.match(dataRouterOpts.state.location);
605 else matches = matchRoutes(routes, { pathname: remainingPathname });
606 warning(parentRoute || matches != null, `No routes matched location "${location.pathname}${location.search}${location.hash}" `);
607 warning(matches == null || matches[matches.length - 1].route.element !== void 0 || matches[matches.length - 1].route.Component !== void 0 || matches[matches.length - 1].route.lazy !== void 0, `Matched leaf route at location "${location.pathname}${location.search}${location.hash}" does not have an element or Component. This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.`);
608 let renderedMatches = _renderMatches(matches && matches.map((match) => Object.assign({}, match, {
609 params: Object.assign({}, parentParams, match.params),
610 pathname: joinPaths([parentPathnameBase, navigator.encodeLocation ? navigator.encodeLocation(match.pathname.replace(/%/g, "%25").replace(/\?/g, "%3F").replace(/#/g, "%23")).pathname : match.pathname]),
611 pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase, navigator.encodeLocation ? navigator.encodeLocation(match.pathnameBase.replace(/%/g, "%25").replace(/\?/g, "%3F").replace(/#/g, "%23")).pathname : match.pathnameBase])
612 })), parentMatches, dataRouterOpts);
613 if (locationArg && renderedMatches) return /* @__PURE__ */ React$1.createElement(LocationContext.Provider, { value: {
614 location: {
615 pathname: "/",
616 search: "",
617 hash: "",
618 state: null,
619 key: "default",
620 mask: void 0,
621 ...location
622 },
623 navigationType: "POP"
624 } }, renderedMatches);
625 return renderedMatches;
626}
627function DefaultErrorComponent() {
628 let error = useRouteError();
629 let message = isRouteErrorResponse(error) ? `${error.status} ${error.statusText}` : error instanceof Error ? error.message : JSON.stringify(error);
630 let stack = error instanceof Error ? error.stack : null;
631 let lightgrey = "rgba(200,200,200, 0.5)";
632 let preStyles = {
633 padding: "0.5rem",
634 backgroundColor: lightgrey
635 };
636 let codeStyles = {
637 padding: "2px 4px",
638 backgroundColor: lightgrey
639 };
640 let devInfo = null;
641 console.error("Error handled by React Router default ErrorBoundary:", error);
642 devInfo = /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement("p", null, "💿 Hey developer 👋"), /* @__PURE__ */ React$1.createElement("p", null, "You can provide a way better UX than this when your app throws errors by providing your own ", /* @__PURE__ */ React$1.createElement("code", { style: codeStyles }, "ErrorBoundary"), " or", " ", /* @__PURE__ */ React$1.createElement("code", { style: codeStyles }, "errorElement"), " prop on your route."));
643 return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement("h2", null, "Unexpected Application Error!"), /* @__PURE__ */ React$1.createElement("h3", { style: { fontStyle: "italic" } }, message), stack ? /* @__PURE__ */ React$1.createElement("pre", { style: preStyles }, stack) : null, devInfo);
644}
645const defaultErrorElement = /* @__PURE__ */ React$1.createElement(DefaultErrorComponent, null);
646var RenderErrorBoundary = class extends React$1.Component {
647 constructor(props) {
648 super(props);
649 this.state = {
650 location: props.location,
651 revalidation: props.revalidation,
652 error: props.error
653 };
654 }
655 static contextType = RSCRouterContext;
656 static getDerivedStateFromError(error) {
657 return { error };
658 }
659 static getDerivedStateFromProps(props, state) {
660 if (state.location !== props.location || state.revalidation !== "idle" && props.revalidation === "idle") return {
661 error: props.error,
662 location: props.location,
663 revalidation: props.revalidation
664 };
665 return {
666 error: props.error !== void 0 ? props.error : state.error,
667 location: state.location,
668 revalidation: props.revalidation || state.revalidation
669 };
670 }
671 componentDidCatch(error, errorInfo) {
672 if (this.props.onError) this.props.onError(error, errorInfo);
673 else console.error("React Router caught the following error during render", error);
674 }
675 render() {
676 let error = this.state.error;
677 if (this.context && typeof error === "object" && error && "digest" in error && typeof error.digest === "string") {
678 const decoded = decodeRouteErrorResponseDigest(error.digest);
679 if (decoded) error = decoded;
680 }
681 let result = error !== void 0 ? /* @__PURE__ */ React$1.createElement(RouteContext.Provider, { value: this.props.routeContext }, /* @__PURE__ */ React$1.createElement(IsDataRouteContext.Provider, { value: this.props.routeContext.isDataRoute }, /* @__PURE__ */ React$1.createElement(RouteIdContext.Provider, { value: this.props.routeContext.matches[this.props.routeContext.matches.length - 1]?.route.id }, /* @__PURE__ */ React$1.createElement(RouteErrorContext.Provider, {
682 value: error,
683 children: this.props.component
684 })))) : this.props.children;
685 if (this.context) return /* @__PURE__ */ React$1.createElement(RSCErrorHandler, { error }, result);
686 return result;
687 }
688};
689const errorRedirectHandledMap = /* @__PURE__ */ new WeakMap();
690function RSCErrorHandler({ children, error }) {
691 let { basename, navigator } = React$1.useContext(NavigationContext);
692 if (typeof error === "object" && error && "digest" in error && typeof error.digest === "string") {
693 let redirect = decodeRedirectErrorDigest(error.digest);
694 if (redirect) {
695 let existingRedirect = errorRedirectHandledMap.get(error);
696 if (existingRedirect) throw existingRedirect;
697 let parsed = parseToInfo(redirect.location, basename);
698 let target = parsed.absoluteURL || parsed.to;
699 validateNavigationTarget(redirect.location, target, getNavigatorCurrentUrl(navigator), "allow-explicit");
700 if (hasInvalidProtocol(target)) throw new Error("Invalid redirect location");
701 if (isBrowser && !errorRedirectHandledMap.get(error)) if (parsed.isExternal || redirect.reloadDocument) window.location.href = target;
702 else {
703 const redirectPromise = Promise.resolve().then(() => window.__reactRouterDataRouter.navigate(parsed.to, { replace: redirect.replace }));
704 errorRedirectHandledMap.set(error, redirectPromise);
705 throw redirectPromise;
706 }
707 return /* @__PURE__ */ React$1.createElement("meta", {
708 httpEquiv: "refresh",
709 content: `0;url=${target}`
710 });
711 }
712 }
713 return children;
714}
715function RenderedRoute({ routeContext, match, children }) {
716 let dataRouterContext = React$1.useContext(DataRouterContext);
717 if (dataRouterContext && dataRouterContext.static && dataRouterContext.staticContext && (match.route.errorElement || match.route.ErrorBoundary)) dataRouterContext.staticContext._deepestRenderedBoundaryId = match.route.id;
718 return /* @__PURE__ */ React$1.createElement(RouteContext.Provider, { value: routeContext }, /* @__PURE__ */ React$1.createElement(IsDataRouteContext.Provider, { value: routeContext.isDataRoute }, /* @__PURE__ */ React$1.createElement(RouteIdContext.Provider, { value: match.route.id }, children)));
719}
720function _renderMatches(matches, parentMatches = [], dataRouterOpts) {
721 let dataRouterState = dataRouterOpts?.state;
722 if (matches == null) {
723 if (!dataRouterState) return null;
724 if (dataRouterState.errors) matches = dataRouterState.matches;
725 else if (parentMatches.length === 0 && !dataRouterState.initialized && dataRouterState.matches.length > 0) matches = dataRouterState.matches;
726 else return null;
727 }
728 let renderedMatches = matches;
729 let errors = dataRouterState?.errors;
730 if (errors != null) {
731 let errorIndex = renderedMatches.findIndex((m) => m.route.id && errors?.[m.route.id] !== void 0);
732 invariant(errorIndex >= 0, `Could not find a matching route for errors on route IDs: ${Object.keys(errors).join(",")}`);
733 renderedMatches = renderedMatches.slice(0, Math.min(renderedMatches.length, errorIndex + 1));
734 }
735 let renderFallback = false;
736 let fallbackIndex = -1;
737 if (dataRouterOpts && dataRouterState) {
738 renderFallback = dataRouterState.renderFallback;
739 for (let i = 0; i < renderedMatches.length; i++) {
740 let match = renderedMatches[i];
741 if (match.route.HydrateFallback || match.route.hydrateFallbackElement) fallbackIndex = i;
742 if (match.route.id) {
743 let { loaderData, errors } = dataRouterState;
744 let needsToRunLoader = match.route.loader && !loaderData.hasOwnProperty(match.route.id) && (!errors || errors[match.route.id] === void 0);
745 if (match.route.lazy || needsToRunLoader) {
746 if (dataRouterOpts.isStatic) renderFallback = true;
747 if (fallbackIndex >= 0) renderedMatches = renderedMatches.slice(0, fallbackIndex + 1);
748 else renderedMatches = [renderedMatches[0]];
749 break;
750 }
751 }
752 }
753 }
754 let onErrorHandler = dataRouterOpts?.onError;
755 let onError = dataRouterState && onErrorHandler ? (error, errorInfo) => {
756 onErrorHandler(error, {
757 location: dataRouterState.location,
758 params: dataRouterState.matches?.[0]?.params ?? {},
759 pattern: getRoutePattern(dataRouterState.matches),
760 errorInfo
761 });
762 } : void 0;
763 return renderedMatches.reduceRight((outlet, match, index) => {
764 let error;
765 let shouldRenderHydrateFallback = false;
766 let errorElement = null;
767 let hydrateFallbackElement = null;
768 if (dataRouterState) {
769 error = errors && match.route.id ? errors[match.route.id] : void 0;
770 errorElement = match.route.errorElement || defaultErrorElement;
771 if (renderFallback) {
772 if (fallbackIndex < 0 && index === 0) {
773 warningOnce("route-fallback", false, "No `HydrateFallback` element provided to render during initial hydration");
774 shouldRenderHydrateFallback = true;
775 hydrateFallbackElement = null;
776 } else if (fallbackIndex === index) {
777 shouldRenderHydrateFallback = true;
778 hydrateFallbackElement = match.route.hydrateFallbackElement || null;
779 }
780 }
781 }
782 let matches = parentMatches.concat(renderedMatches.slice(0, index + 1));
783 let getChildren = () => {
784 let children;
785 if (error) children = errorElement;
786 else if (shouldRenderHydrateFallback) children = hydrateFallbackElement;
787 else if (match.route.Component) children = /* @__PURE__ */ React$1.createElement(match.route.Component, null);
788 else if (match.route.element) children = match.route.element;
789 else children = outlet;
790 return /* @__PURE__ */ React$1.createElement(RenderedRoute, {
791 match,
792 routeContext: {
793 outlet,
794 matches,
795 isDataRoute: dataRouterState != null
796 },
797 children
798 });
799 };
800 return dataRouterState && (match.route.ErrorBoundary || match.route.errorElement || index === 0) ? /* @__PURE__ */ React$1.createElement(RenderErrorBoundary, {
801 location: dataRouterState.location,
802 revalidation: dataRouterState.revalidation,
803 component: errorElement,
804 error,
805 children: getChildren(),
806 routeContext: {
807 outlet: null,
808 matches,
809 isDataRoute: true
810 },
811 onError
812 }) : getChildren();
813 }, null);
814}
815function getDataRouterConsoleError(hookName) {
816 return `${hookName} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`;
817}
818function useDataRouterContext(hookName) {
819 let ctx = React$1.useContext(DataRouterContext);
820 invariant(ctx, getDataRouterConsoleError(hookName));
821 return ctx;
822}
823function useDataRouterState(hookName) {
824 let state = React$1.useContext(DataRouterStateContext);
825 invariant(state, getDataRouterConsoleError(hookName));
826 return state;
827}
828function useDataRouterFetchers(hookName) {
829 let fetchers = React$1.useContext(FetchersContext);
830 invariant(fetchers, getDataRouterConsoleError(hookName));
831 return fetchers;
832}
833function useDataRouterData(hookName) {
834 let data = React$1.useContext(DataRouterDataContext);
835 invariant(data, getDataRouterConsoleError(hookName));
836 return data;
837}
838function useDataRouterNavigation(hookName) {
839 let navigation = React$1.useContext(DataRouterNavigationContext);
840 invariant(navigation, getDataRouterConsoleError(hookName));
841 return navigation;
842}
843function useCurrentRouteId(hookName) {
844 let routeId = React$1.useContext(RouteIdContext);
845 invariant(routeId, `${hookName} can only be used on routes that contain a unique "id"`);
846 return routeId;
847}
848/**
849* Returns the current {@link Navigation}, defaulting to an "idle" navigation
850* when no navigation is in progress. You can use this to render pending UI
851* (like a global spinner) or read [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
852* from a form navigation.
853*
854* @example
855* import { useNavigation } from "react-router";
856*
857* function SomeComponent() {
858* let navigation = useNavigation();
859* navigation.state;
860* navigation.formData;
861* // etc.
862* }
863*
864* @public
865* @category Hooks
866* @mode framework
867* @mode data
868* @returns The current {@link Navigation} object
869*/
870function useNavigation() {
871 let { navigation } = useDataRouterNavigation("useNavigation");
872 return React$1.useMemo(() => {
873 let { matches, historyAction, ...rest } = navigation;
874 return rest;
875 }, [navigation]);
876}
877/**
878* Revalidate the data on the page for reasons outside of normal data mutations
879* like [`Window` focus](https://developer.mozilla.org/en-US/docs/Web/API/Window/focus_event)
880* or polling on an interval.
881*
882* Note that page data is already revalidated automatically after actions.
883* If you find yourself using this for normal CRUD operations on your data in
884* response to user interactions, you're probably not taking advantage of the
885* other APIs like {@link useFetcher}, {@link Form}, {@link useSubmit} that do
886* this automatically.
887*
888* @example
889* import { useRevalidator } from "react-router";
890*
891* function WindowFocusRevalidator() {
892* const revalidator = useRevalidator();
893*
894* useFakeWindowFocus(() => {
895* revalidator.revalidate();
896* });
897*
898* return (
899* <div hidden={revalidator.state === "idle"}>
900* Revalidating...
901* </div>
902* );
903* }
904*
905* @public
906* @category Hooks
907* @mode framework
908* @mode data
909* @returns An object with a `revalidate` function and the current revalidation
910* `state`
911*/
912function useRevalidator() {
913 let dataRouterContext = useDataRouterContext("useRevalidator");
914 let { revalidation } = useDataRouterNavigation("useRevalidator");
915 let revalidate = React$1.useCallback(async () => {
916 await dataRouterContext.router.revalidate();
917 }, [dataRouterContext.router]);
918 return React$1.useMemo(() => ({
919 revalidate,
920 state: revalidation
921 }), [revalidate, revalidation]);
922}
923/**
924* Returns the active route matches, useful for accessing `loaderData` for
925* parent/child routes or the route [`handle`](../../start/framework/route-module#handle)
926* property
927*
928* Pairing the route `handle` with `useMatches` gets very powerful since you can put
929* whatever you want on a route handle and have access to `useMatches` anywhere.
930* Please see the [handle](../../how-to/using-handle) documentation for an example
931* of breadcrumbs via `useMatches`/`handle`.
932*
933* ```tsx
934* import { useMatches } from "react-router";
935*
936* function SomeComponent() {
937* const matches = useMatches();
938* // matches[i].id // route id
939* // matches[i].pathname // the portion of the URL the route matched
940* // matches[i].params // the parsed params from the URL
941* // matches[i].loaderData // the data from the loader
942* // matches[i].handle // the route handle with any app specific data
943* }
944* ```
945*
946* <docs-info>useMatches only works with a data router like `createBrowserRouter`,
947* since they know the full route tree up front and can provide all of the current
948* matches. Additionally, `useMatches` will not match down into any descendant route
949* trees since the router isn't aware of the descendant routes.</docs-info>
950*
951* @public
952* @category Hooks
953* @mode framework
954* @mode data
955* @returns An array of {@link UIMatch | UI matches} for the current route hierarchy
956*/
957function useMatches() {
958 let { matches } = useDataRouterState("useMatches");
959 let { loaderData } = useDataRouterData("useMatches");
960 return React$1.useMemo(() => matches.map((m) => convertRouteMatchToUiMatch(m, loaderData)), [matches, loaderData]);
961}
962/**
963* Returns the data from the closest route
964* [`loader`](../../start/framework/route-module#loader) or
965* [`clientLoader`](../../start/framework/route-module#clientloader).
966*
967* @example
968* import { useLoaderData } from "react-router";
969*
970* export async function loader() {
971* return await fakeDb.invoices.findAll();
972* }
973*
974* export default function Invoices() {
975* let invoices = useLoaderData<typeof loader>();
976* // ...
977* }
978*
979* @public
980* @category Hooks
981* @mode framework
982* @mode data
983* @returns The data returned from the route's [`loader`](../../start/framework/route-module#loader) or [`clientLoader`](../../start/framework/route-module#clientloader) function
984*/
985function useLoaderData() {
986 let data = useDataRouterData("useLoaderData");
987 let routeId = useCurrentRouteId("useLoaderData");
988 return data.loaderData[routeId];
989}
990/**
991* Returns the [`loader`](../../start/framework/route-module#loader) data for a
992* given route by route ID.
993*
994* Route IDs are created automatically. They are simply the path of the route file
995* relative to the app folder without the extension.
996*
997* | Route Filename | Route ID |
998* | ---------------------------- | ---------------------- |
999* | `app/root.tsx` | `"root"` |
1000* | `app/routes/teams.tsx` | `"routes/teams"` |
1001* | `app/whatever/teams.$id.tsx` | `"whatever/teams.$id"` |
1002*
1003* @example
1004* import { useRouteLoaderData } from "react-router";
1005*
1006* function SomeComponent() {
1007* const { user } = useRouteLoaderData("root");
1008* }
1009*
1010* // You can also specify your own route ID's manually in your routes.ts file:
1011* route("/", "containers/app.tsx", { id: "app" })
1012* useRouteLoaderData("app");
1013*
1014* @public
1015* @category Hooks
1016* @mode framework
1017* @mode data
1018* @param routeId The ID of the route to return loader data from
1019* @returns The data returned from the specified route's [`loader`](../../start/framework/route-module#loader)
1020* function, or `undefined` if not found
1021*/
1022function useRouteLoaderData(routeId) {
1023 return useDataRouterData("useRouteLoaderData").loaderData[routeId];
1024}
1025/**
1026* Returns the [`action`](../../start/framework/route-module#action) data from
1027* the most recent `POST` navigation form submission or `undefined` if there
1028* hasn't been one.
1029*
1030* @example
1031* import { Form, useActionData } from "react-router";
1032*
1033* export async function action({ request }) {
1034* const body = await request.formData();
1035* const name = body.get("visitorsName");
1036* return { message: `Hello, ${name}` };
1037* }
1038*
1039* export default function Invoices() {
1040* const data = useActionData();
1041* return (
1042* <Form method="post">
1043* <input type="text" name="visitorsName" />
1044* {data ? data.message : "Waiting..."}
1045* </Form>
1046* );
1047* }
1048*
1049* @public
1050* @category Hooks
1051* @mode framework
1052* @mode data
1053* @returns The data returned from the route's [`action`](../../start/framework/route-module#action)
1054* function, or `undefined` if no [`action`](../../start/framework/route-module#action)
1055* has been called
1056*/
1057function useActionData() {
1058 let data = useDataRouterData("useActionData");
1059 let routeId = useCurrentRouteId("useActionData");
1060 return data.actionData ? data.actionData[routeId] : void 0;
1061}
1062/**
1063* Accesses the error thrown during an
1064* [`action`](../../start/framework/route-module#action),
1065* [`loader`](../../start/framework/route-module#loader),
1066* or component render to be used in a route module
1067* [`ErrorBoundary`](../../start/framework/route-module#errorboundary).
1068*
1069* @example
1070* export function ErrorBoundary() {
1071* const error = useRouteError();
1072* return <div>{error.message}</div>;
1073* }
1074*
1075* @public
1076* @category Hooks
1077* @mode framework
1078* @mode data
1079* @returns The error that was thrown during route [loading](../../start/framework/route-module#loader),
1080* [`action`](../../start/framework/route-module#action) execution, or rendering
1081*/
1082function useRouteError() {
1083 let error = React$1.useContext(RouteErrorContext);
1084 let data = useDataRouterData("useRouteError");
1085 let routeId = useCurrentRouteId("useRouteError");
1086 if (error !== void 0) return error;
1087 return data.errors?.[routeId];
1088}
1089/**
1090* Returns the resolved promise value from the closest {@link Await | `<Await>`}.
1091*
1092* @example
1093* function SomeDescendant() {
1094* const value = useAsyncValue();
1095* // ...
1096* }
1097*
1098* // somewhere in your app
1099* <Await resolve={somePromise}>
1100* <SomeDescendant />
1101* </Await>;
1102*
1103* @public
1104* @category Hooks
1105* @mode framework
1106* @mode data
1107* @returns The resolved value from the nearest {@link Await} component
1108*/
1109function useAsyncValue() {
1110 let value = React$1.useContext(AwaitContext);
1111 return value && "status" in value && !("_tracked" in value) ? value.status === "fulfilled" ? value.value : void 0 : value?._data;
1112}
1113/**
1114* Returns the rejection value from the closest {@link Await | `<Await>`}.
1115*
1116* @example
1117* import { Await, useAsyncError } from "react-router";
1118*
1119* function ErrorElement() {
1120* const error = useAsyncError();
1121* return (
1122* <p>Uh Oh, something went wrong! {error.message}</p>
1123* );
1124* }
1125*
1126* // somewhere in your app
1127* <Await
1128* resolve={promiseThatRejects}
1129* errorElement={<ErrorElement />}
1130* />;
1131*
1132* @public
1133* @category Hooks
1134* @mode framework
1135* @mode data
1136* @returns The error that was thrown in the nearest {@link Await} component
1137*/
1138function useAsyncError() {
1139 let value = React$1.useContext(AwaitContext);
1140 return value && "status" in value && !("_tracked" in value) ? value.status === "rejected" ? value.reason : void 0 : value?._error;
1141}
1142let blockerId = 0;
1143/**
1144* Allow the application to block navigations within the SPA and present the
1145* user a confirmation dialog to confirm the navigation. Mostly used to avoid
1146* using half-filled form data. This does not handle hard-reloads or
1147* cross-origin navigations.
1148*
1149* The {@link Blocker} object returned by the hook has the following properties:
1150*
1151* - **`state`**
1152* - `unblocked` - the blocker is idle and has not prevented any navigation
1153* - `blocked` - the blocker has prevented a navigation
1154* - `proceeding` - the blocker is proceeding through from a blocked navigation
1155* - **`location`**
1156* - When in a `blocked` state, this represents the {@link Location} to which
1157* we blocked a navigation. When in a `proceeding` state, this is the
1158* location being navigated to after a `blocker.proceed()` call.
1159* - **`proceed()`**
1160* - When in a `blocked` state, you may call `blocker.proceed()` to proceed to
1161* the blocked location.
1162* - **`reset()`**
1163* - When in a `blocked` state, you may call `blocker.reset()` to return the
1164* blocker to an `unblocked` state and leave the user at the current
1165* location.
1166*
1167* @example
1168* // Boolean version
1169* let blocker = useBlocker(value !== "");
1170*
1171* // Function version
1172* let blocker = useBlocker(
1173* ({ currentLocation, nextLocation, historyAction }) =>
1174* value !== "" &&
1175* currentLocation.pathname !== nextLocation.pathname
1176* );
1177*
1178* @additionalExamples
1179* ```tsx
1180* import { useCallback, useState } from "react";
1181* import { BlockerFunction, useBlocker } from "react-router";
1182*
1183* export function ImportantForm() {
1184* const [value, setValue] = useState("");
1185*
1186* const shouldBlock = useCallback<BlockerFunction>(
1187* () => value !== "",
1188* [value]
1189* );
1190* const blocker = useBlocker(shouldBlock);
1191*
1192* return (
1193* <form
1194* onSubmit={(e) => {
1195* e.preventDefault();
1196* setValue("");
1197* if (blocker.state === "blocked") {
1198* blocker.proceed();
1199* }
1200* }}
1201* >
1202* <input
1203* name="data"
1204* value={value}
1205* onChange={(e) => setValue(e.target.value)}
1206* />
1207*
1208* <button type="submit">Save</button>
1209*
1210* {blocker.state === "blocked" ? (
1211* <>
1212* <p style={{ color: "red" }}>
1213* Blocked the last navigation to
1214* </p>
1215* <button
1216* type="button"
1217* onClick={() => blocker.proceed()}
1218* >
1219* Let me through
1220* </button>
1221* <button
1222* type="button"
1223* onClick={() => blocker.reset()}
1224* >
1225* Keep me here
1226* </button>
1227* </>
1228* ) : blocker.state === "proceeding" ? (
1229* <p style={{ color: "orange" }}>
1230* Proceeding through blocked navigation
1231* </p>
1232* ) : (
1233* <p style={{ color: "green" }}>
1234* Blocker is currently unblocked
1235* </p>
1236* )}
1237* </form>
1238* );
1239* }
1240* ```
1241*
1242* @public
1243* @category Hooks
1244* @mode framework
1245* @mode data
1246* @param shouldBlock Either a boolean or a function returning a boolean which
1247* indicates whether the navigation should be blocked. The function format
1248* receives a single object parameter containing the `currentLocation`,
1249* `nextLocation`, and `historyAction` of the potential navigation.
1250* @returns A {@link Blocker} object with state and reset functionality
1251*/
1252function useBlocker(shouldBlock) {
1253 let { router, basename } = useDataRouterContext("useBlocker");
1254 let state = useDataRouterState("useBlocker");
1255 let [blockerKey, setBlockerKey] = React$1.useState("");
1256 let blockerFunction = React$1.useCallback((arg) => {
1257 if (typeof shouldBlock !== "function") return !!shouldBlock;
1258 if (basename === "/") return shouldBlock(arg);
1259 let { currentLocation, nextLocation, historyAction } = arg;
1260 return shouldBlock({
1261 currentLocation: {
1262 ...currentLocation,
1263 pathname: stripBasename(currentLocation.pathname, basename) || currentLocation.pathname
1264 },
1265 nextLocation: {
1266 ...nextLocation,
1267 pathname: stripBasename(nextLocation.pathname, basename) || nextLocation.pathname
1268 },
1269 historyAction
1270 });
1271 }, [basename, shouldBlock]);
1272 React$1.useEffect(() => {
1273 let key = String(++blockerId);
1274 setBlockerKey(key);
1275 return () => router.deleteBlocker(key);
1276 }, [router]);
1277 React$1.useEffect(() => {
1278 if (blockerKey !== "") router.getBlocker(blockerKey, blockerFunction);
1279 }, [
1280 router,
1281 blockerKey,
1282 blockerFunction
1283 ]);
1284 return blockerKey && state.blockers.has(blockerKey) ? state.blockers.get(blockerKey) : IDLE_BLOCKER;
1285}
1286function useNavigateStable() {
1287 let { router } = useDataRouterContext("useNavigate");
1288 let id = useCurrentRouteId("useNavigate");
1289 let activeRef = React$1.useRef(false);
1290 React$1.useLayoutEffect(() => {
1291 activeRef.current = true;
1292 });
1293 return React$1.useCallback(async (to, options = {}) => {
1294 warning(activeRef.current, navigateEffectWarning);
1295 if (!activeRef.current) return;
1296 if (typeof to === "number") await router.navigate(to);
1297 else await router.navigate(to, {
1298 fromRouteId: id,
1299 ...options
1300 });
1301 }, [router, id]);
1302}
1303const alreadyWarned = {};
1304function warningOnce(key, cond, message) {
1305 if (!cond && !alreadyWarned[key]) {
1306 alreadyWarned[key] = true;
1307 warning(false, message);
1308 }
1309}
1310function useRoute(...args) {
1311 const currentRouteId = useCurrentRouteId("useRoute");
1312 const id = args[0] ?? currentRouteId;
1313 const state = useDataRouterState("useRoute");
1314 const data = useDataRouterData("useRoute");
1315 const route = state.matches.find(({ route }) => route.id === id);
1316 if (route === void 0) return void 0;
1317 return {
1318 handle: route.route.handle,
1319 loaderData: data.loaderData[id],
1320 actionData: data.actionData?.[id]
1321 };
1322}
1323function toRouterStateMatch(match) {
1324 return {
1325 id: match.route.id,
1326 pathname: match.pathname,
1327 params: match.params,
1328 handle: match.route.handle
1329 };
1330}
1331/**
1332* A unified hook for reading router state: current (`active`) and in-flight
1333* (`pending`) locations, search params, params, matches, and navigation type.
1334*
1335* This hook consolidates the information you used to get from {@link useLocation},
1336* {@link useSearchParams}, {@link useParams}, {@link useMatches}, {@link useNavigation},
1337* and {@link useNavigationType} into a single hook.
1338*
1339*
1340* @example
1341* import { unstable_useRouterState as useRouterState } from "react-router";
1342*
1343* let { active, pending } = useRouterState();
1344*
1345* // Active is always populated with the current location
1346* active.location; // replaces `useLocation()`
1347* active.searchParams; // replaces `useSearchParams()[0]`
1348* active.params; // replaces `useParams()`
1349* active.matches; // replaces `useMatches()`
1350* active.type; // replaces `useNavigationType()`
1351*
1352* // Pending is only populated during a navigation
1353* pending.location; // replaces `useNavigation().location`
1354* pending.searchParams; // equivalent to `new URLSearchParams(useNavigation().search)`
1355* pending.params; // Not directly accessible today
1356* pending.matches; // Not directly accessible today
1357* pending.type; // Not directly accessible today
1358* pending.state; // replaces `useNavigation().state`
1359* pending.formMethod; // replaces useNavigation().formMethod
1360* pending.formAction; // replaces useNavigation().formAction
1361* pending.formEncType; // replaces useNavigation().formEncType
1362* pending.formData; // replaces useNavigation().formData
1363* pending.json; // replaces useNavigation().json
1364* pending.text; // replaces useNavigation().text
1365*
1366* @name unstable_useRouterState
1367* @public
1368* @category Hooks
1369* @mode framework
1370* @mode data
1371* @returns The current router state with `active` and `pending` variants
1372*/
1373function useRouterState() {
1374 let { location, historyAction: type, matches } = useDataRouterState("unstable_useRouterState");
1375 let { navigation } = useDataRouterNavigation("unstable_useRouterState");
1376 let active = React$1.useMemo(() => ({
1377 type,
1378 location,
1379 searchParams: new URLSearchParams(location.search),
1380 params: matches[matches.length - 1]?.params ?? {},
1381 matches: matches.map((m) => toRouterStateMatch(m))
1382 }), [
1383 location,
1384 matches,
1385 type
1386 ]);
1387 let pending = React$1.useMemo(() => {
1388 if (navigation.state === "idle") return null;
1389 let shared = {
1390 type: navigation.historyAction,
1391 location: navigation.location,
1392 searchParams: new URLSearchParams(navigation.location.search),
1393 params: navigation.matches[navigation.matches.length - 1]?.params ?? {},
1394 matches: navigation.matches.map((m) => toRouterStateMatch(m))
1395 };
1396 return navigation.state === "loading" ? {
1397 ...shared,
1398 state: "loading",
1399 formMethod: navigation.formMethod,
1400 formAction: navigation.formAction,
1401 formEncType: navigation.formEncType,
1402 formData: navigation.formData,
1403 json: navigation.json,
1404 text: navigation.text
1405 } : {
1406 ...shared,
1407 state: "submitting",
1408 formMethod: navigation.formMethod,
1409 formAction: navigation.formAction,
1410 formEncType: navigation.formEncType,
1411 formData: navigation.formData,
1412 json: navigation.json,
1413 text: navigation.text
1414 };
1415 }, [navigation]);
1416 return React$1.useMemo(() => ({
1417 active,
1418 pending
1419 }), [active, pending]);
1420}
1421//#endregion
1422export { _renderMatches, useActionData, useAsyncError, useAsyncValue, useBlocker, useCurrentRouteId, useDataRouterContext, useDataRouterData, useDataRouterFetchers, useDataRouterState, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRoute, useRouteError, useRouteLoaderData, useRouterState, useRoutes, useRoutesImpl };