UNPKG

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