UNPKG

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