UNPKG

46.2 kBJavaScriptView Raw
1/**
2 * React Router v6.12.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 * as React from 'react';
12import { UNSAFE_invariant, joinPaths, matchPath, UNSAFE_getPathContributingMatches, UNSAFE_warning, resolveTo, parsePath, matchRoutes, Action, isRouteErrorResponse, createMemoryHistory, stripBasename, AbortedDeferredError, createRouter } from '@remix-run/router';
13export { AbortedDeferredError, Action as NavigationType, createPath, defer, generatePath, isRouteErrorResponse, json, matchPath, matchRoutes, parsePath, redirect, resolvePath } from '@remix-run/router';
14
15const DataRouterContext = /*#__PURE__*/React.createContext(null);
16{
17 DataRouterContext.displayName = "DataRouter";
18}
19const DataRouterStateContext = /*#__PURE__*/React.createContext(null);
20{
21 DataRouterStateContext.displayName = "DataRouterState";
22}
23const AwaitContext = /*#__PURE__*/React.createContext(null);
24{
25 AwaitContext.displayName = "Await";
26}
27const NavigationContext = /*#__PURE__*/React.createContext(null);
28{
29 NavigationContext.displayName = "Navigation";
30}
31const LocationContext = /*#__PURE__*/React.createContext(null);
32{
33 LocationContext.displayName = "Location";
34}
35const RouteContext = /*#__PURE__*/React.createContext({
36 outlet: null,
37 matches: [],
38 isDataRoute: false
39});
40{
41 RouteContext.displayName = "Route";
42}
43const RouteErrorContext = /*#__PURE__*/React.createContext(null);
44{
45 RouteErrorContext.displayName = "RouteError";
46}
47
48/**
49 * Returns the full href for the given "to" value. This is useful for building
50 * custom links that are also accessible and preserve right-click behavior.
51 *
52 * @see https://reactrouter.com/hooks/use-href
53 */
54function useHref(to, {
55 relative
56} = {}) {
57 !useInRouterContext() ? UNSAFE_invariant(false,
58 // TODO: This error is probably because they somehow have 2 versions of the
59 // router loaded. We can help them understand how to avoid that.
60 `useHref() may be used only in the context of a <Router> component.`) : void 0;
61 let {
62 basename,
63 navigator
64 } = React.useContext(NavigationContext);
65 let {
66 hash,
67 pathname,
68 search
69 } = useResolvedPath(to, {
70 relative
71 });
72 let joinedPathname = pathname;
73 // If we're operating within a basename, prepend it to the pathname prior
74 // to creating the href. If this is a root navigation, then just use the raw
75 // basename which allows the basename to have full control over the presence
76 // of a trailing slash on root links
77 if (basename !== "/") {
78 joinedPathname = pathname === "/" ? basename : joinPaths([basename, pathname]);
79 }
80 return navigator.createHref({
81 pathname: joinedPathname,
82 search,
83 hash
84 });
85}
86/**
87 * Returns true if this component is a descendant of a <Router>.
88 *
89 * @see https://reactrouter.com/hooks/use-in-router-context
90 */
91function useInRouterContext() {
92 return React.useContext(LocationContext) != null;
93}
94/**
95 * Returns the current location object, which represents the current URL in web
96 * browsers.
97 *
98 * Note: If you're using this it may mean you're doing some of your own
99 * "routing" in your app, and we'd like to know what your use case is. We may
100 * be able to provide something higher-level to better suit your needs.
101 *
102 * @see https://reactrouter.com/hooks/use-location
103 */
104function useLocation() {
105 !useInRouterContext() ? UNSAFE_invariant(false,
106 // TODO: This error is probably because they somehow have 2 versions of the
107 // router loaded. We can help them understand how to avoid that.
108 `useLocation() may be used only in the context of a <Router> component.`) : void 0;
109 return React.useContext(LocationContext).location;
110}
111/**
112 * Returns the current navigation action which describes how the router came to
113 * the current location, either by a pop, push, or replace on the history stack.
114 *
115 * @see https://reactrouter.com/hooks/use-navigation-type
116 */
117function useNavigationType() {
118 return React.useContext(LocationContext).navigationType;
119}
120/**
121 * Returns a PathMatch object if the given pattern matches the current URL.
122 * This is useful for components that need to know "active" state, e.g.
123 * <NavLink>.
124 *
125 * @see https://reactrouter.com/hooks/use-match
126 */
127function useMatch(pattern) {
128 !useInRouterContext() ? UNSAFE_invariant(false,
129 // TODO: This error is probably because they somehow have 2 versions of the
130 // router loaded. We can help them understand how to avoid that.
131 `useMatch() may be used only in the context of a <Router> component.`) : void 0;
132 let {
133 pathname
134 } = useLocation();
135 return React.useMemo(() => matchPath(pattern, pathname), [pathname, pattern]);
136}
137const navigateEffectWarning = `You should call navigate() in a React.useEffect(), not when ` + `your component is first rendered.`;
138// Mute warnings for calls to useNavigate in SSR environments
139function useIsomorphicLayoutEffect(cb) {
140 let isStatic = React.useContext(NavigationContext).static;
141 if (!isStatic) {
142 // We should be able to get rid of this once react 18.3 is released
143 // See: https://github.com/facebook/react/pull/26395
144 // eslint-disable-next-line react-hooks/rules-of-hooks
145 React.useLayoutEffect(cb);
146 }
147}
148/**
149 * Returns an imperative method for changing the location. Used by <Link>s, but
150 * may also be used by other elements to change the location.
151 *
152 * @see https://reactrouter.com/hooks/use-navigate
153 */
154function useNavigate() {
155 let {
156 isDataRoute
157 } = React.useContext(RouteContext);
158 // Conditional usage is OK here because the usage of a data router is static
159 // eslint-disable-next-line react-hooks/rules-of-hooks
160 return isDataRoute ? useNavigateStable() : useNavigateUnstable();
161}
162function useNavigateUnstable() {
163 !useInRouterContext() ? UNSAFE_invariant(false,
164 // TODO: This error is probably because they somehow have 2 versions of the
165 // router loaded. We can help them understand how to avoid that.
166 `useNavigate() may be used only in the context of a <Router> component.`) : void 0;
167 let dataRouterContext = React.useContext(DataRouterContext);
168 let {
169 basename,
170 navigator
171 } = React.useContext(NavigationContext);
172 let {
173 matches
174 } = React.useContext(RouteContext);
175 let {
176 pathname: locationPathname
177 } = useLocation();
178 let routePathnamesJson = JSON.stringify(UNSAFE_getPathContributingMatches(matches).map(match => match.pathnameBase));
179 let activeRef = React.useRef(false);
180 useIsomorphicLayoutEffect(() => {
181 activeRef.current = true;
182 });
183 let navigate = React.useCallback((to, options = {}) => {
184 UNSAFE_warning(activeRef.current, navigateEffectWarning) ;
185 // Short circuit here since if this happens on first render the navigate
186 // is useless because we haven't wired up our history listener yet
187 if (!activeRef.current) return;
188 if (typeof to === "number") {
189 navigator.go(to);
190 return;
191 }
192 let path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, options.relative === "path");
193 // If we're operating within a basename, prepend it to the pathname prior
194 // to handing off to history (but only if we're not in a data router,
195 // otherwise it'll prepend the basename inside of the router).
196 // If this is a root navigation, then we navigate to the raw basename
197 // which allows the basename to have full control over the presence of a
198 // trailing slash on root links
199 if (dataRouterContext == null && basename !== "/") {
200 path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
201 }
202 (!!options.replace ? navigator.replace : navigator.push)(path, options.state, options);
203 }, [basename, navigator, routePathnamesJson, locationPathname, dataRouterContext]);
204 return navigate;
205}
206const OutletContext = /*#__PURE__*/React.createContext(null);
207/**
208 * Returns the context (if provided) for the child route at this level of the route
209 * hierarchy.
210 * @see https://reactrouter.com/hooks/use-outlet-context
211 */
212function useOutletContext() {
213 return React.useContext(OutletContext);
214}
215/**
216 * Returns the element for the child route at this level of the route
217 * hierarchy. Used internally by <Outlet> to render child routes.
218 *
219 * @see https://reactrouter.com/hooks/use-outlet
220 */
221function useOutlet(context) {
222 let outlet = React.useContext(RouteContext).outlet;
223 if (outlet) {
224 return /*#__PURE__*/React.createElement(OutletContext.Provider, {
225 value: context
226 }, outlet);
227 }
228 return outlet;
229}
230/**
231 * Returns an object of key/value pairs of the dynamic params from the current
232 * URL that were matched by the route path.
233 *
234 * @see https://reactrouter.com/hooks/use-params
235 */
236function useParams() {
237 let {
238 matches
239 } = React.useContext(RouteContext);
240 let routeMatch = matches[matches.length - 1];
241 return routeMatch ? routeMatch.params : {};
242}
243/**
244 * Resolves the pathname of the given `to` value against the current location.
245 *
246 * @see https://reactrouter.com/hooks/use-resolved-path
247 */
248function useResolvedPath(to, {
249 relative
250} = {}) {
251 let {
252 matches
253 } = React.useContext(RouteContext);
254 let {
255 pathname: locationPathname
256 } = useLocation();
257 let routePathnamesJson = JSON.stringify(UNSAFE_getPathContributingMatches(matches).map(match => match.pathnameBase));
258 return React.useMemo(() => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, relative === "path"), [to, routePathnamesJson, locationPathname, relative]);
259}
260/**
261 * Returns the element of the route that matched the current location, prepared
262 * with the correct context to render the remainder of the route tree. Route
263 * elements in the tree must render an <Outlet> to render their child route's
264 * element.
265 *
266 * @see https://reactrouter.com/hooks/use-routes
267 */
268function useRoutes(routes, locationArg) {
269 return useRoutesImpl(routes, locationArg);
270}
271// Internal implementation with accept optional param for RouterProvider usage
272function useRoutesImpl(routes, locationArg, dataRouterState) {
273 !useInRouterContext() ? UNSAFE_invariant(false,
274 // TODO: This error is probably because they somehow have 2 versions of the
275 // router loaded. We can help them understand how to avoid that.
276 `useRoutes() may be used only in the context of a <Router> component.`) : void 0;
277 let {
278 navigator
279 } = React.useContext(NavigationContext);
280 let {
281 matches: parentMatches
282 } = React.useContext(RouteContext);
283 let routeMatch = parentMatches[parentMatches.length - 1];
284 let parentParams = routeMatch ? routeMatch.params : {};
285 let parentPathname = routeMatch ? routeMatch.pathname : "/";
286 let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/";
287 let parentRoute = routeMatch && routeMatch.route;
288 {
289 // You won't get a warning about 2 different <Routes> under a <Route>
290 // without a trailing *, but this is a best-effort warning anyway since we
291 // cannot even give the warning unless they land at the parent route.
292 //
293 // Example:
294 //
295 // <Routes>
296 // {/* This route path MUST end with /* because otherwise
297 // it will never match /blog/post/123 */}
298 // <Route path="blog" element={<Blog />} />
299 // <Route path="blog/feed" element={<BlogFeed />} />
300 // </Routes>
301 //
302 // function Blog() {
303 // return (
304 // <Routes>
305 // <Route path="post/:id" element={<Post />} />
306 // </Routes>
307 // );
308 // }
309 let parentPath = parentRoute && parentRoute.path || "";
310 warningOnce(parentPathname, !parentRoute || parentPath.endsWith("*"), `You rendered descendant <Routes> (or called \`useRoutes()\`) at ` + `"${parentPathname}" (under <Route path="${parentPath}">) but the ` + `parent route path has no trailing "*". This means if you navigate ` + `deeper, the parent won't match anymore and therefore the child ` + `routes will never render.\n\n` + `Please change the parent <Route path="${parentPath}"> to <Route ` + `path="${parentPath === "/" ? "*" : `${parentPath}/*`}">.`);
311 }
312 let locationFromContext = useLocation();
313 let location;
314 if (locationArg) {
315 let parsedLocationArg = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
316 !(parentPathnameBase === "/" || parsedLocationArg.pathname?.startsWith(parentPathnameBase)) ? UNSAFE_invariant(false, `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.`) : void 0;
317 location = parsedLocationArg;
318 } else {
319 location = locationFromContext;
320 }
321 let pathname = location.pathname || "/";
322 let remainingPathname = parentPathnameBase === "/" ? pathname : pathname.slice(parentPathnameBase.length) || "/";
323 let matches = matchRoutes(routes, {
324 pathname: remainingPathname
325 });
326 {
327 UNSAFE_warning(parentRoute || matches != null, `No routes matched location "${location.pathname}${location.search}${location.hash}" `) ;
328 UNSAFE_warning(matches == null || matches[matches.length - 1].route.element !== undefined || matches[matches.length - 1].route.Component !== undefined, `Matched leaf route at location "${location.pathname}${location.search}${location.hash}" ` + `does not have an element or Component. This means it will render an <Outlet /> with a ` + `null value by default resulting in an "empty" page.`) ;
329 }
330 let renderedMatches = _renderMatches(matches && matches.map(match => Object.assign({}, match, {
331 params: Object.assign({}, parentParams, match.params),
332 pathname: joinPaths([parentPathnameBase,
333 // Re-encode pathnames that were decoded inside matchRoutes
334 navigator.encodeLocation ? navigator.encodeLocation(match.pathname).pathname : match.pathname]),
335 pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase,
336 // Re-encode pathnames that were decoded inside matchRoutes
337 navigator.encodeLocation ? navigator.encodeLocation(match.pathnameBase).pathname : match.pathnameBase])
338 })), parentMatches, dataRouterState);
339 // When a user passes in a `locationArg`, the associated routes need to
340 // be wrapped in a new `LocationContext.Provider` in order for `useLocation`
341 // to use the scoped location instead of the global location.
342 if (locationArg && renderedMatches) {
343 return /*#__PURE__*/React.createElement(LocationContext.Provider, {
344 value: {
345 location: {
346 pathname: "/",
347 search: "",
348 hash: "",
349 state: null,
350 key: "default",
351 ...location
352 },
353 navigationType: Action.Pop
354 }
355 }, renderedMatches);
356 }
357 return renderedMatches;
358}
359function DefaultErrorComponent() {
360 let error = useRouteError();
361 let message = isRouteErrorResponse(error) ? `${error.status} ${error.statusText}` : error instanceof Error ? error.message : JSON.stringify(error);
362 let stack = error instanceof Error ? error.stack : null;
363 let lightgrey = "rgba(200,200,200, 0.5)";
364 let preStyles = {
365 padding: "0.5rem",
366 backgroundColor: lightgrey
367 };
368 let codeStyles = {
369 padding: "2px 4px",
370 backgroundColor: lightgrey
371 };
372 let devInfo = null;
373 {
374 console.error("Error handled by React Router default ErrorBoundary:", error);
375 devInfo = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("p", null, "\uD83D\uDCBF Hey developer \uD83D\uDC4B"), /*#__PURE__*/React.createElement("p", null, "You can provide a way better UX than this when your app throws errors by providing your own ", /*#__PURE__*/React.createElement("code", {
376 style: codeStyles
377 }, "ErrorBoundary"), " or", " ", /*#__PURE__*/React.createElement("code", {
378 style: codeStyles
379 }, "errorElement"), " prop on your route."));
380 }
381 return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h2", null, "Unexpected Application Error!"), /*#__PURE__*/React.createElement("h3", {
382 style: {
383 fontStyle: "italic"
384 }
385 }, message), stack ? /*#__PURE__*/React.createElement("pre", {
386 style: preStyles
387 }, stack) : null, devInfo);
388}
389const defaultErrorElement = /*#__PURE__*/React.createElement(DefaultErrorComponent, null);
390class RenderErrorBoundary extends React.Component {
391 constructor(props) {
392 super(props);
393 this.state = {
394 location: props.location,
395 revalidation: props.revalidation,
396 error: props.error
397 };
398 }
399 static getDerivedStateFromError(error) {
400 return {
401 error: error
402 };
403 }
404 static getDerivedStateFromProps(props, state) {
405 // When we get into an error state, the user will likely click "back" to the
406 // previous page that didn't have an error. Because this wraps the entire
407 // application, that will have no effect--the error page continues to display.
408 // This gives us a mechanism to recover from the error when the location changes.
409 //
410 // Whether we're in an error state or not, we update the location in state
411 // so that when we are in an error state, it gets reset when a new location
412 // comes in and the user recovers from the error.
413 if (state.location !== props.location || state.revalidation !== "idle" && props.revalidation === "idle") {
414 return {
415 error: props.error,
416 location: props.location,
417 revalidation: props.revalidation
418 };
419 }
420 // If we're not changing locations, preserve the location but still surface
421 // any new errors that may come through. We retain the existing error, we do
422 // this because the error provided from the app state may be cleared without
423 // the location changing.
424 return {
425 error: props.error || state.error,
426 location: state.location,
427 revalidation: props.revalidation || state.revalidation
428 };
429 }
430 componentDidCatch(error, errorInfo) {
431 console.error("React Router caught the following error during render", error, errorInfo);
432 }
433 render() {
434 return this.state.error ? /*#__PURE__*/React.createElement(RouteContext.Provider, {
435 value: this.props.routeContext
436 }, /*#__PURE__*/React.createElement(RouteErrorContext.Provider, {
437 value: this.state.error,
438 children: this.props.component
439 })) : this.props.children;
440 }
441}
442function RenderedRoute({
443 routeContext,
444 match,
445 children
446}) {
447 let dataRouterContext = React.useContext(DataRouterContext);
448 // Track how deep we got in our render pass to emulate SSR componentDidCatch
449 // in a DataStaticRouter
450 if (dataRouterContext && dataRouterContext.static && dataRouterContext.staticContext && (match.route.errorElement || match.route.ErrorBoundary)) {
451 dataRouterContext.staticContext._deepestRenderedBoundaryId = match.route.id;
452 }
453 return /*#__PURE__*/React.createElement(RouteContext.Provider, {
454 value: routeContext
455 }, children);
456}
457function _renderMatches(matches, parentMatches = [], dataRouterState = null) {
458 if (matches == null) {
459 if (dataRouterState?.errors) {
460 // Don't bail if we have data router errors so we can render them in the
461 // boundary. Use the pre-matched (or shimmed) matches
462 matches = dataRouterState.matches;
463 } else {
464 return null;
465 }
466 }
467 let renderedMatches = matches;
468 // If we have data errors, trim matches to the highest error boundary
469 let errors = dataRouterState?.errors;
470 if (errors != null) {
471 let errorIndex = renderedMatches.findIndex(m => m.route.id && errors?.[m.route.id]);
472 !(errorIndex >= 0) ? UNSAFE_invariant(false, `Could not find a matching route for errors on route IDs: ${Object.keys(errors).join(",")}`) : void 0;
473 renderedMatches = renderedMatches.slice(0, Math.min(renderedMatches.length, errorIndex + 1));
474 }
475 return renderedMatches.reduceRight((outlet, match, index) => {
476 let error = match.route.id ? errors?.[match.route.id] : null;
477 // Only data routers handle errors
478 let errorElement = null;
479 if (dataRouterState) {
480 errorElement = match.route.errorElement || defaultErrorElement;
481 }
482 let matches = parentMatches.concat(renderedMatches.slice(0, index + 1));
483 let getChildren = () => {
484 let children;
485 if (error) {
486 children = errorElement;
487 } else if (match.route.Component) {
488 // Note: This is a de-optimized path since React won't re-use the
489 // ReactElement since it's identity changes with each new
490 // React.createElement call. We keep this so folks can use
491 // `<Route Component={...}>` in `<Routes>` but generally `Component`
492 // usage is only advised in `RouterProvider` when we can convert it to
493 // `element` ahead of time.
494 children = /*#__PURE__*/React.createElement(match.route.Component, null);
495 } else if (match.route.element) {
496 children = match.route.element;
497 } else {
498 children = outlet;
499 }
500 return /*#__PURE__*/React.createElement(RenderedRoute, {
501 match: match,
502 routeContext: {
503 outlet,
504 matches,
505 isDataRoute: dataRouterState != null
506 },
507 children: children
508 });
509 };
510 // Only wrap in an error boundary within data router usages when we have an
511 // ErrorBoundary/errorElement on this route. Otherwise let it bubble up to
512 // an ancestor ErrorBoundary/errorElement
513 return dataRouterState && (match.route.ErrorBoundary || match.route.errorElement || index === 0) ? /*#__PURE__*/React.createElement(RenderErrorBoundary, {
514 location: dataRouterState.location,
515 revalidation: dataRouterState.revalidation,
516 component: errorElement,
517 error: error,
518 children: getChildren(),
519 routeContext: {
520 outlet: null,
521 matches,
522 isDataRoute: true
523 }
524 }) : getChildren();
525 }, null);
526}
527var DataRouterHook;
528(function (DataRouterHook) {
529 DataRouterHook["UseBlocker"] = "useBlocker";
530 DataRouterHook["UseRevalidator"] = "useRevalidator";
531 DataRouterHook["UseNavigateStable"] = "useNavigate";
532})(DataRouterHook || (DataRouterHook = {}));
533var DataRouterStateHook;
534(function (DataRouterStateHook) {
535 DataRouterStateHook["UseBlocker"] = "useBlocker";
536 DataRouterStateHook["UseLoaderData"] = "useLoaderData";
537 DataRouterStateHook["UseActionData"] = "useActionData";
538 DataRouterStateHook["UseRouteError"] = "useRouteError";
539 DataRouterStateHook["UseNavigation"] = "useNavigation";
540 DataRouterStateHook["UseRouteLoaderData"] = "useRouteLoaderData";
541 DataRouterStateHook["UseMatches"] = "useMatches";
542 DataRouterStateHook["UseRevalidator"] = "useRevalidator";
543 DataRouterStateHook["UseNavigateStable"] = "useNavigate";
544 DataRouterStateHook["UseRouteId"] = "useRouteId";
545})(DataRouterStateHook || (DataRouterStateHook = {}));
546function getDataRouterConsoleError(hookName) {
547 return `${hookName} must be used within a data router. See https://reactrouter.com/routers/picking-a-router.`;
548}
549function useDataRouterContext(hookName) {
550 let ctx = React.useContext(DataRouterContext);
551 !ctx ? UNSAFE_invariant(false, getDataRouterConsoleError(hookName)) : void 0;
552 return ctx;
553}
554function useDataRouterState(hookName) {
555 let state = React.useContext(DataRouterStateContext);
556 !state ? UNSAFE_invariant(false, getDataRouterConsoleError(hookName)) : void 0;
557 return state;
558}
559function useRouteContext(hookName) {
560 let route = React.useContext(RouteContext);
561 !route ? UNSAFE_invariant(false, getDataRouterConsoleError(hookName)) : void 0;
562 return route;
563}
564// Internal version with hookName-aware debugging
565function useCurrentRouteId(hookName) {
566 let route = useRouteContext(hookName);
567 let thisRoute = route.matches[route.matches.length - 1];
568 !thisRoute.route.id ? UNSAFE_invariant(false, `${hookName} can only be used on routes that contain a unique "id"`) : void 0;
569 return thisRoute.route.id;
570}
571/**
572 * Returns the ID for the nearest contextual route
573 */
574function useRouteId() {
575 return useCurrentRouteId(DataRouterStateHook.UseRouteId);
576}
577/**
578 * Returns the current navigation, defaulting to an "idle" navigation when
579 * no navigation is in progress
580 */
581function useNavigation() {
582 let state = useDataRouterState(DataRouterStateHook.UseNavigation);
583 return state.navigation;
584}
585/**
586 * Returns a revalidate function for manually triggering revalidation, as well
587 * as the current state of any manual revalidations
588 */
589function useRevalidator() {
590 let dataRouterContext = useDataRouterContext(DataRouterHook.UseRevalidator);
591 let state = useDataRouterState(DataRouterStateHook.UseRevalidator);
592 return {
593 revalidate: dataRouterContext.router.revalidate,
594 state: state.revalidation
595 };
596}
597/**
598 * Returns the active route matches, useful for accessing loaderData for
599 * parent/child routes or the route "handle" property
600 */
601function useMatches() {
602 let {
603 matches,
604 loaderData
605 } = useDataRouterState(DataRouterStateHook.UseMatches);
606 return React.useMemo(() => matches.map(match => {
607 let {
608 pathname,
609 params
610 } = match;
611 // Note: This structure matches that created by createUseMatchesMatch
612 // in the @remix-run/router , so if you change this please also change
613 // that :) Eventually we'll DRY this up
614 return {
615 id: match.route.id,
616 pathname,
617 params,
618 data: loaderData[match.route.id],
619 handle: match.route.handle
620 };
621 }), [matches, loaderData]);
622}
623/**
624 * Returns the loader data for the nearest ancestor Route loader
625 */
626function useLoaderData() {
627 let state = useDataRouterState(DataRouterStateHook.UseLoaderData);
628 let routeId = useCurrentRouteId(DataRouterStateHook.UseLoaderData);
629 if (state.errors && state.errors[routeId] != null) {
630 console.error(`You cannot \`useLoaderData\` in an errorElement (routeId: ${routeId})`);
631 return undefined;
632 }
633 return state.loaderData[routeId];
634}
635/**
636 * Returns the loaderData for the given routeId
637 */
638function useRouteLoaderData(routeId) {
639 let state = useDataRouterState(DataRouterStateHook.UseRouteLoaderData);
640 return state.loaderData[routeId];
641}
642/**
643 * Returns the action data for the nearest ancestor Route action
644 */
645function useActionData() {
646 let state = useDataRouterState(DataRouterStateHook.UseActionData);
647 let route = React.useContext(RouteContext);
648 !route ? UNSAFE_invariant(false, `useActionData must be used inside a RouteContext`) : void 0;
649 return Object.values(state?.actionData || {})[0];
650}
651/**
652 * Returns the nearest ancestor Route error, which could be a loader/action
653 * error or a render error. This is intended to be called from your
654 * ErrorBoundary/errorElement to display a proper error message.
655 */
656function useRouteError() {
657 let error = React.useContext(RouteErrorContext);
658 let state = useDataRouterState(DataRouterStateHook.UseRouteError);
659 let routeId = useCurrentRouteId(DataRouterStateHook.UseRouteError);
660 // If this was a render error, we put it in a RouteError context inside
661 // of RenderErrorBoundary
662 if (error) {
663 return error;
664 }
665 // Otherwise look for errors from our data router state
666 return state.errors?.[routeId];
667}
668/**
669 * Returns the happy-path data from the nearest ancestor <Await /> value
670 */
671function useAsyncValue() {
672 let value = React.useContext(AwaitContext);
673 return value?._data;
674}
675/**
676 * Returns the error from the nearest ancestor <Await /> value
677 */
678function useAsyncError() {
679 let value = React.useContext(AwaitContext);
680 return value?._error;
681}
682let blockerId = 0;
683/**
684 * Allow the application to block navigations within the SPA and present the
685 * user a confirmation dialog to confirm the navigation. Mostly used to avoid
686 * using half-filled form data. This does not handle hard-reloads or
687 * cross-origin navigations.
688 */
689function useBlocker(shouldBlock) {
690 let {
691 router
692 } = useDataRouterContext(DataRouterHook.UseBlocker);
693 let state = useDataRouterState(DataRouterStateHook.UseBlocker);
694 let [blockerKey] = React.useState(() => String(++blockerId));
695 let blockerFunction = React.useCallback(args => {
696 return typeof shouldBlock === "function" ? !!shouldBlock(args) : !!shouldBlock;
697 }, [shouldBlock]);
698 let blocker = router.getBlocker(blockerKey, blockerFunction);
699 // Cleanup on unmount
700 React.useEffect(() => () => router.deleteBlocker(blockerKey), [router, blockerKey]);
701 // Prefer the blocker from state since DataRouterContext is memoized so this
702 // ensures we update on blocker state updates
703 return state.blockers.get(blockerKey) || blocker;
704}
705/**
706 * Stable version of useNavigate that is used when we are in the context of
707 * a RouterProvider.
708 */
709function useNavigateStable() {
710 let {
711 router
712 } = useDataRouterContext(DataRouterHook.UseNavigateStable);
713 let id = useCurrentRouteId(DataRouterStateHook.UseNavigateStable);
714 let activeRef = React.useRef(false);
715 useIsomorphicLayoutEffect(() => {
716 activeRef.current = true;
717 });
718 let navigate = React.useCallback((to, options = {}) => {
719 UNSAFE_warning(activeRef.current, navigateEffectWarning) ;
720 // Short circuit here since if this happens on first render the navigate
721 // is useless because we haven't wired up our router subscriber yet
722 if (!activeRef.current) return;
723 if (typeof to === "number") {
724 router.navigate(to);
725 } else {
726 router.navigate(to, {
727 fromRouteId: id,
728 ...options
729 });
730 }
731 }, [router, id]);
732 return navigate;
733}
734const alreadyWarned = {};
735function warningOnce(key, cond, message) {
736 if (!cond && !alreadyWarned[key]) {
737 alreadyWarned[key] = true;
738 UNSAFE_warning(false, message) ;
739 }
740}
741
742/**
743 * Given a Remix Router instance, render the appropriate UI
744 */
745function RouterProvider({
746 fallbackElement,
747 router
748}) {
749 // Need to use a layout effect here so we are subscribed early enough to
750 // pick up on any render-driven redirects/navigations (useEffect/<Navigate>)
751 let [state, setStateImpl] = React.useState(router.state);
752 let setState = React.useCallback(newState => {
753 "startTransition" in React ? React.startTransition(() => setStateImpl(newState)) : setStateImpl(newState);
754 }, [setStateImpl]);
755 React.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
756 let navigator = React.useMemo(() => {
757 return {
758 createHref: router.createHref,
759 encodeLocation: router.encodeLocation,
760 go: n => router.navigate(n),
761 push: (to, state, opts) => router.navigate(to, {
762 state,
763 preventScrollReset: opts?.preventScrollReset
764 }),
765 replace: (to, state, opts) => router.navigate(to, {
766 replace: true,
767 state,
768 preventScrollReset: opts?.preventScrollReset
769 })
770 };
771 }, [router]);
772 let basename = router.basename || "/";
773 let dataRouterContext = React.useMemo(() => ({
774 router,
775 navigator,
776 static: false,
777 basename
778 }), [router, navigator, basename]);
779 // The fragment and {null} here are important! We need them to keep React 18's
780 // useId happy when we are server-rendering since we may have a <script> here
781 // containing the hydrated server-side staticContext (from StaticRouterProvider).
782 // useId relies on the component tree structure to generate deterministic id's
783 // so we need to ensure it remains the same on the client even though
784 // we don't need the <script> tag
785 return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(DataRouterContext.Provider, {
786 value: dataRouterContext
787 }, /*#__PURE__*/React.createElement(DataRouterStateContext.Provider, {
788 value: state
789 }, /*#__PURE__*/React.createElement(Router, {
790 basename: basename,
791 location: state.location,
792 navigationType: state.historyAction,
793 navigator: navigator
794 }, state.initialized ? /*#__PURE__*/React.createElement(DataRoutes, {
795 routes: router.routes,
796 state: state
797 }) : fallbackElement))), null);
798}
799function DataRoutes({
800 routes,
801 state
802}) {
803 return useRoutesImpl(routes, undefined, state);
804}
805/**
806 * A <Router> that stores all entries in memory.
807 *
808 * @see https://reactrouter.com/router-components/memory-router
809 */
810function MemoryRouter({
811 basename,
812 children,
813 initialEntries,
814 initialIndex
815}) {
816 let historyRef = React.useRef();
817 if (historyRef.current == null) {
818 historyRef.current = createMemoryHistory({
819 initialEntries,
820 initialIndex,
821 v5Compat: true
822 });
823 }
824 let history = historyRef.current;
825 let [state, setStateImpl] = React.useState({
826 action: history.action,
827 location: history.location
828 });
829 let setState = React.useCallback(newState => {
830 "startTransition" in React ? React.startTransition(() => setStateImpl(newState)) : setStateImpl(newState);
831 }, [setStateImpl]);
832 React.useLayoutEffect(() => history.listen(setState), [history, setState]);
833 return /*#__PURE__*/React.createElement(Router, {
834 basename: basename,
835 children: children,
836 location: state.location,
837 navigationType: state.action,
838 navigator: history
839 });
840}
841/**
842 * Changes the current location.
843 *
844 * Note: This API is mostly useful in React.Component subclasses that are not
845 * able to use hooks. In functional components, we recommend you use the
846 * `useNavigate` hook instead.
847 *
848 * @see https://reactrouter.com/components/navigate
849 */
850function Navigate({
851 to,
852 replace,
853 state,
854 relative
855}) {
856 !useInRouterContext() ? UNSAFE_invariant(false,
857 // TODO: This error is probably because they somehow have 2 versions of
858 // the router loaded. We can help them understand how to avoid that.
859 `<Navigate> may be used only in the context of a <Router> component.`) : void 0;
860 UNSAFE_warning(!React.useContext(NavigationContext).static, `<Navigate> must not be used on the initial render in a <StaticRouter>. ` + `This is a no-op, but you should modify your code so the <Navigate> is ` + `only ever rendered in response to some user interaction or state change.`) ;
861 let {
862 matches
863 } = React.useContext(RouteContext);
864 let {
865 pathname: locationPathname
866 } = useLocation();
867 let navigate = useNavigate();
868 // Resolve the path outside of the effect so that when effects run twice in
869 // StrictMode they navigate to the same place
870 let path = resolveTo(to, UNSAFE_getPathContributingMatches(matches).map(match => match.pathnameBase), locationPathname, relative === "path");
871 let jsonPath = JSON.stringify(path);
872 React.useEffect(() => navigate(JSON.parse(jsonPath), {
873 replace,
874 state,
875 relative
876 }), [navigate, jsonPath, relative, replace, state]);
877 return null;
878}
879/**
880 * Renders the child route's element, if there is one.
881 *
882 * @see https://reactrouter.com/components/outlet
883 */
884function Outlet(props) {
885 return useOutlet(props.context);
886}
887/**
888 * Declares an element that should be rendered at a certain URL path.
889 *
890 * @see https://reactrouter.com/components/route
891 */
892function Route(_props) {
893 UNSAFE_invariant(false, `A <Route> is only ever to be used as the child of <Routes> element, ` + `never rendered directly. Please wrap your <Route> in a <Routes>.`) ;
894}
895/**
896 * Provides location context for the rest of the app.
897 *
898 * Note: You usually won't render a <Router> directly. Instead, you'll render a
899 * router that is more specific to your environment such as a <BrowserRouter>
900 * in web browsers or a <StaticRouter> for server rendering.
901 *
902 * @see https://reactrouter.com/router-components/router
903 */
904function Router({
905 basename: basenameProp = "/",
906 children = null,
907 location: locationProp,
908 navigationType = Action.Pop,
909 navigator,
910 static: staticProp = false
911}) {
912 !!useInRouterContext() ? UNSAFE_invariant(false, `You cannot render a <Router> inside another <Router>.` + ` You should never have more than one in your app.`) : void 0;
913 // Preserve trailing slashes on basename, so we can let the user control
914 // the enforcement of trailing slashes throughout the app
915 let basename = basenameProp.replace(/^\/*/, "/");
916 let navigationContext = React.useMemo(() => ({
917 basename,
918 navigator,
919 static: staticProp
920 }), [basename, navigator, staticProp]);
921 if (typeof locationProp === "string") {
922 locationProp = parsePath(locationProp);
923 }
924 let {
925 pathname = "/",
926 search = "",
927 hash = "",
928 state = null,
929 key = "default"
930 } = locationProp;
931 let locationContext = React.useMemo(() => {
932 let trailingPathname = stripBasename(pathname, basename);
933 if (trailingPathname == null) {
934 return null;
935 }
936 return {
937 location: {
938 pathname: trailingPathname,
939 search,
940 hash,
941 state,
942 key
943 },
944 navigationType
945 };
946 }, [basename, pathname, search, hash, state, key, navigationType]);
947 UNSAFE_warning(locationContext != null, `<Router basename="${basename}"> is not able to match the URL ` + `"${pathname}${search}${hash}" because it does not start with the ` + `basename, so the <Router> won't render anything.`) ;
948 if (locationContext == null) {
949 return null;
950 }
951 return /*#__PURE__*/React.createElement(NavigationContext.Provider, {
952 value: navigationContext
953 }, /*#__PURE__*/React.createElement(LocationContext.Provider, {
954 children: children,
955 value: locationContext
956 }));
957}
958/**
959 * A container for a nested tree of <Route> elements that renders the branch
960 * that best matches the current location.
961 *
962 * @see https://reactrouter.com/components/routes
963 */
964function Routes({
965 children,
966 location
967}) {
968 return useRoutes(createRoutesFromChildren(children), location);
969}
970/**
971 * Component to use for rendering lazily loaded data from returning defer()
972 * in a loader function
973 */
974function Await({
975 children,
976 errorElement,
977 resolve
978}) {
979 return /*#__PURE__*/React.createElement(AwaitErrorBoundary, {
980 resolve: resolve,
981 errorElement: errorElement
982 }, /*#__PURE__*/React.createElement(ResolveAwait, null, children));
983}
984var AwaitRenderStatus;
985(function (AwaitRenderStatus) {
986 AwaitRenderStatus[AwaitRenderStatus["pending"] = 0] = "pending";
987 AwaitRenderStatus[AwaitRenderStatus["success"] = 1] = "success";
988 AwaitRenderStatus[AwaitRenderStatus["error"] = 2] = "error";
989})(AwaitRenderStatus || (AwaitRenderStatus = {}));
990const neverSettledPromise = new Promise(() => {});
991class AwaitErrorBoundary extends React.Component {
992 constructor(props) {
993 super(props);
994 this.state = {
995 error: null
996 };
997 }
998 static getDerivedStateFromError(error) {
999 return {
1000 error
1001 };
1002 }
1003 componentDidCatch(error, errorInfo) {
1004 console.error("<Await> caught the following error during render", error, errorInfo);
1005 }
1006 render() {
1007 let {
1008 children,
1009 errorElement,
1010 resolve
1011 } = this.props;
1012 let promise = null;
1013 let status = AwaitRenderStatus.pending;
1014 if (!(resolve instanceof Promise)) {
1015 // Didn't get a promise - provide as a resolved promise
1016 status = AwaitRenderStatus.success;
1017 promise = Promise.resolve();
1018 Object.defineProperty(promise, "_tracked", {
1019 get: () => true
1020 });
1021 Object.defineProperty(promise, "_data", {
1022 get: () => resolve
1023 });
1024 } else if (this.state.error) {
1025 // Caught a render error, provide it as a rejected promise
1026 status = AwaitRenderStatus.error;
1027 let renderError = this.state.error;
1028 promise = Promise.reject().catch(() => {}); // Avoid unhandled rejection warnings
1029 Object.defineProperty(promise, "_tracked", {
1030 get: () => true
1031 });
1032 Object.defineProperty(promise, "_error", {
1033 get: () => renderError
1034 });
1035 } else if (resolve._tracked) {
1036 // Already tracked promise - check contents
1037 promise = resolve;
1038 status = promise._error !== undefined ? AwaitRenderStatus.error : promise._data !== undefined ? AwaitRenderStatus.success : AwaitRenderStatus.pending;
1039 } else {
1040 // Raw (untracked) promise - track it
1041 status = AwaitRenderStatus.pending;
1042 Object.defineProperty(resolve, "_tracked", {
1043 get: () => true
1044 });
1045 promise = resolve.then(data => Object.defineProperty(resolve, "_data", {
1046 get: () => data
1047 }), error => Object.defineProperty(resolve, "_error", {
1048 get: () => error
1049 }));
1050 }
1051 if (status === AwaitRenderStatus.error && promise._error instanceof AbortedDeferredError) {
1052 // Freeze the UI by throwing a never resolved promise
1053 throw neverSettledPromise;
1054 }
1055 if (status === AwaitRenderStatus.error && !errorElement) {
1056 // No errorElement, throw to the nearest route-level error boundary
1057 throw promise._error;
1058 }
1059 if (status === AwaitRenderStatus.error) {
1060 // Render via our errorElement
1061 return /*#__PURE__*/React.createElement(AwaitContext.Provider, {
1062 value: promise,
1063 children: errorElement
1064 });
1065 }
1066 if (status === AwaitRenderStatus.success) {
1067 // Render children with resolved value
1068 return /*#__PURE__*/React.createElement(AwaitContext.Provider, {
1069 value: promise,
1070 children: children
1071 });
1072 }
1073 // Throw to the suspense boundary
1074 throw promise;
1075 }
1076}
1077/**
1078 * @private
1079 * Indirection to leverage useAsyncValue for a render-prop API on <Await>
1080 */
1081function ResolveAwait({
1082 children
1083}) {
1084 let data = useAsyncValue();
1085 let toRender = typeof children === "function" ? children(data) : children;
1086 return /*#__PURE__*/React.createElement(React.Fragment, null, toRender);
1087}
1088///////////////////////////////////////////////////////////////////////////////
1089// UTILS
1090///////////////////////////////////////////////////////////////////////////////
1091/**
1092 * Creates a route config from a React "children" object, which is usually
1093 * either a `<Route>` element or an array of them. Used internally by
1094 * `<Routes>` to create a route config from its children.
1095 *
1096 * @see https://reactrouter.com/utils/create-routes-from-children
1097 */
1098function createRoutesFromChildren(children, parentPath = []) {
1099 let routes = [];
1100 React.Children.forEach(children, (element, index) => {
1101 if (! /*#__PURE__*/React.isValidElement(element)) {
1102 // Ignore non-elements. This allows people to more easily inline
1103 // conditionals in their route config.
1104 return;
1105 }
1106 let treePath = [...parentPath, index];
1107 if (element.type === React.Fragment) {
1108 // Transparently support React.Fragment and its children.
1109 routes.push.apply(routes, createRoutesFromChildren(element.props.children, treePath));
1110 return;
1111 }
1112 !(element.type === Route) ? UNSAFE_invariant(false, `[${typeof element.type === "string" ? element.type : element.type.name}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`) : void 0;
1113 !(!element.props.index || !element.props.children) ? UNSAFE_invariant(false, "An index route cannot have child routes.") : void 0;
1114 let route = {
1115 id: element.props.id || treePath.join("-"),
1116 caseSensitive: element.props.caseSensitive,
1117 element: element.props.element,
1118 Component: element.props.Component,
1119 index: element.props.index,
1120 path: element.props.path,
1121 loader: element.props.loader,
1122 action: element.props.action,
1123 errorElement: element.props.errorElement,
1124 ErrorBoundary: element.props.ErrorBoundary,
1125 hasErrorBoundary: element.props.ErrorBoundary != null || element.props.errorElement != null,
1126 shouldRevalidate: element.props.shouldRevalidate,
1127 handle: element.props.handle,
1128 lazy: element.props.lazy
1129 };
1130 if (element.props.children) {
1131 route.children = createRoutesFromChildren(element.props.children, treePath);
1132 }
1133 routes.push(route);
1134 });
1135 return routes;
1136}
1137/**
1138 * Renders the result of `matchRoutes()` into a React element.
1139 */
1140function renderMatches(matches) {
1141 return _renderMatches(matches);
1142}
1143
1144function mapRouteProperties(route) {
1145 let updates = {
1146 // Note: this check also occurs in createRoutesFromChildren so update
1147 // there if you change this -- please and thank you!
1148 hasErrorBoundary: route.ErrorBoundary != null || route.errorElement != null
1149 };
1150 if (route.Component) {
1151 {
1152 if (route.element) {
1153 UNSAFE_warning(false, "You should not include both `Component` and `element` on your route - " + "`Component` will be used.") ;
1154 }
1155 }
1156 Object.assign(updates, {
1157 element: /*#__PURE__*/React.createElement(route.Component),
1158 Component: undefined
1159 });
1160 }
1161 if (route.ErrorBoundary) {
1162 {
1163 if (route.errorElement) {
1164 UNSAFE_warning(false, "You should not include both `ErrorBoundary` and `errorElement` on your route - " + "`ErrorBoundary` will be used.") ;
1165 }
1166 }
1167 Object.assign(updates, {
1168 errorElement: /*#__PURE__*/React.createElement(route.ErrorBoundary),
1169 ErrorBoundary: undefined
1170 });
1171 }
1172 return updates;
1173}
1174function createMemoryRouter(routes, opts) {
1175 return createRouter({
1176 basename: opts?.basename,
1177 future: {
1178 ...opts?.future,
1179 v7_prependBasename: true
1180 },
1181 history: createMemoryHistory({
1182 initialEntries: opts?.initialEntries,
1183 initialIndex: opts?.initialIndex
1184 }),
1185 hydrationData: opts?.hydrationData,
1186 routes,
1187 mapRouteProperties
1188 }).initialize();
1189}
1190
1191export { Await, MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, DataRouterContext as UNSAFE_DataRouterContext, DataRouterStateContext as UNSAFE_DataRouterStateContext, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, mapRouteProperties as UNSAFE_mapRouteProperties, useRouteId as UNSAFE_useRouteId, useRoutesImpl as UNSAFE_useRoutesImpl, createMemoryRouter, createRoutesFromChildren, createRoutesFromChildren as createRoutesFromElements, renderMatches, useBlocker as unstable_useBlocker, useActionData, useAsyncError, useAsyncValue, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes };
1192//# sourceMappingURL=react-router.development.js.map