UNPKG

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