UNPKG

48.4 kBJavaScriptView Raw
1/**
2 * React Router v6.14.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, IDLE_BLOCKER, stripBasename, isRouteErrorResponse, createMemoryHistory, 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 basename
693 } = useDataRouterContext(DataRouterHook.UseBlocker);
694 let state = useDataRouterState(DataRouterStateHook.UseBlocker);
695 let [blockerKey, setBlockerKey] = React.useState("");
696 let [blocker, setBlocker] = React.useState(IDLE_BLOCKER);
697 let blockerFunction = React.useCallback(arg => {
698 if (typeof shouldBlock !== "function") {
699 return !!shouldBlock;
700 }
701 if (basename === "/") {
702 return shouldBlock(arg);
703 }
704 // If they provided us a function and we've got an active basename, strip
705 // it from the locations we expose to the user to match the behavior of
706 // useLocation
707 let {
708 currentLocation,
709 nextLocation,
710 historyAction
711 } = arg;
712 return shouldBlock({
713 currentLocation: {
714 ...currentLocation,
715 pathname: stripBasename(currentLocation.pathname, basename) || currentLocation.pathname
716 },
717 nextLocation: {
718 ...nextLocation,
719 pathname: stripBasename(nextLocation.pathname, basename) || nextLocation.pathname
720 },
721 historyAction
722 });
723 }, [basename, shouldBlock]);
724 React.useEffect(() => {
725 let key = String(++blockerId);
726 setBlocker(router.getBlocker(key, blockerFunction));
727 setBlockerKey(key);
728 return () => router.deleteBlocker(key);
729 }, [router, setBlocker, setBlockerKey, blockerFunction]);
730 // Prefer the blocker from state since DataRouterContext is memoized so this
731 // ensures we update on blocker state updates
732 return blockerKey && state.blockers.has(blockerKey) ? state.blockers.get(blockerKey) : blocker;
733}
734/**
735 * Stable version of useNavigate that is used when we are in the context of
736 * a RouterProvider.
737 */
738function useNavigateStable() {
739 let {
740 router
741 } = useDataRouterContext(DataRouterHook.UseNavigateStable);
742 let id = useCurrentRouteId(DataRouterStateHook.UseNavigateStable);
743 let activeRef = React.useRef(false);
744 useIsomorphicLayoutEffect(() => {
745 activeRef.current = true;
746 });
747 let navigate = React.useCallback((to, options = {}) => {
748 UNSAFE_warning(activeRef.current, navigateEffectWarning) ;
749 // Short circuit here since if this happens on first render the navigate
750 // is useless because we haven't wired up our router subscriber yet
751 if (!activeRef.current) return;
752 if (typeof to === "number") {
753 router.navigate(to);
754 } else {
755 router.navigate(to, {
756 fromRouteId: id,
757 ...options
758 });
759 }
760 }, [router, id]);
761 return navigate;
762}
763const alreadyWarned = {};
764function warningOnce(key, cond, message) {
765 if (!cond && !alreadyWarned[key]) {
766 alreadyWarned[key] = true;
767 UNSAFE_warning(false, message) ;
768 }
769}
770
771/**
772 Webpack + React 17 fails to compile on any of the following because webpack
773 complains that `startTransition` doesn't exist in `React`:
774 * import { startTransition } from "react"
775 * import * as React from from "react";
776 "startTransition" in React ? React.startTransition(() => setState()) : setState()
777 * import * as React from from "react";
778 "startTransition" in React ? React["startTransition"](() => setState()) : setState()
779
780 Moving it to a constant such as the following solves the Webpack/React 17 issue:
781 * import * as React from from "react";
782 const START_TRANSITION = "startTransition";
783 START_TRANSITION in React ? React[START_TRANSITION](() => setState()) : setState()
784
785 However, that introduces webpack/terser minification issues in production builds
786 in React 18 where minification/obfuscation ends up removing the call of
787 React.startTransition entirely from the first half of the ternary. Grabbing
788 this exported reference once up front resolves that issue.
789
790 See https://github.com/remix-run/react-router/issues/10579
791*/
792const START_TRANSITION = "startTransition";
793const startTransitionImpl = React[START_TRANSITION];
794/**
795 * Given a Remix Router instance, render the appropriate UI
796 */
797function RouterProvider({
798 fallbackElement,
799 router,
800 future
801}) {
802 // Need to use a layout effect here so we are subscribed early enough to
803 // pick up on any render-driven redirects/navigations (useEffect/<Navigate>)
804 let [state, setStateImpl] = React.useState(router.state);
805 let {
806 v7_startTransition
807 } = future || {};
808 let setState = React.useCallback(newState => {
809 v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
810 }, [setStateImpl, v7_startTransition]);
811 React.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
812 let navigator = React.useMemo(() => {
813 return {
814 createHref: router.createHref,
815 encodeLocation: router.encodeLocation,
816 go: n => router.navigate(n),
817 push: (to, state, opts) => router.navigate(to, {
818 state,
819 preventScrollReset: opts?.preventScrollReset
820 }),
821 replace: (to, state, opts) => router.navigate(to, {
822 replace: true,
823 state,
824 preventScrollReset: opts?.preventScrollReset
825 })
826 };
827 }, [router]);
828 let basename = router.basename || "/";
829 let dataRouterContext = React.useMemo(() => ({
830 router,
831 navigator,
832 static: false,
833 basename
834 }), [router, navigator, basename]);
835 // The fragment and {null} here are important! We need them to keep React 18's
836 // useId happy when we are server-rendering since we may have a <script> here
837 // containing the hydrated server-side staticContext (from StaticRouterProvider).
838 // useId relies on the component tree structure to generate deterministic id's
839 // so we need to ensure it remains the same on the client even though
840 // we don't need the <script> tag
841 return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(DataRouterContext.Provider, {
842 value: dataRouterContext
843 }, /*#__PURE__*/React.createElement(DataRouterStateContext.Provider, {
844 value: state
845 }, /*#__PURE__*/React.createElement(Router, {
846 basename: basename,
847 location: state.location,
848 navigationType: state.historyAction,
849 navigator: navigator
850 }, state.initialized ? /*#__PURE__*/React.createElement(DataRoutes, {
851 routes: router.routes,
852 state: state
853 }) : fallbackElement))), null);
854}
855function DataRoutes({
856 routes,
857 state
858}) {
859 return useRoutesImpl(routes, undefined, state);
860}
861/**
862 * A <Router> that stores all entries in memory.
863 *
864 * @see https://reactrouter.com/router-components/memory-router
865 */
866function MemoryRouter({
867 basename,
868 children,
869 initialEntries,
870 initialIndex,
871 future
872}) {
873 let historyRef = React.useRef();
874 if (historyRef.current == null) {
875 historyRef.current = createMemoryHistory({
876 initialEntries,
877 initialIndex,
878 v5Compat: true
879 });
880 }
881 let history = historyRef.current;
882 let [state, setStateImpl] = React.useState({
883 action: history.action,
884 location: history.location
885 });
886 let {
887 v7_startTransition
888 } = future || {};
889 let setState = React.useCallback(newState => {
890 v7_startTransition && startTransitionImpl ? startTransitionImpl(() => setStateImpl(newState)) : setStateImpl(newState);
891 }, [setStateImpl, v7_startTransition]);
892 React.useLayoutEffect(() => history.listen(setState), [history, setState]);
893 return /*#__PURE__*/React.createElement(Router, {
894 basename: basename,
895 children: children,
896 location: state.location,
897 navigationType: state.action,
898 navigator: history
899 });
900}
901/**
902 * Changes the current location.
903 *
904 * Note: This API is mostly useful in React.Component subclasses that are not
905 * able to use hooks. In functional components, we recommend you use the
906 * `useNavigate` hook instead.
907 *
908 * @see https://reactrouter.com/components/navigate
909 */
910function Navigate({
911 to,
912 replace,
913 state,
914 relative
915}) {
916 !useInRouterContext() ? UNSAFE_invariant(false,
917 // TODO: This error is probably because they somehow have 2 versions of
918 // the router loaded. We can help them understand how to avoid that.
919 `<Navigate> may be used only in the context of a <Router> component.`) : void 0;
920 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.`) ;
921 let {
922 matches
923 } = React.useContext(RouteContext);
924 let {
925 pathname: locationPathname
926 } = useLocation();
927 let navigate = useNavigate();
928 // Resolve the path outside of the effect so that when effects run twice in
929 // StrictMode they navigate to the same place
930 let path = resolveTo(to, UNSAFE_getPathContributingMatches(matches).map(match => match.pathnameBase), locationPathname, relative === "path");
931 let jsonPath = JSON.stringify(path);
932 React.useEffect(() => navigate(JSON.parse(jsonPath), {
933 replace,
934 state,
935 relative
936 }), [navigate, jsonPath, relative, replace, state]);
937 return null;
938}
939/**
940 * Renders the child route's element, if there is one.
941 *
942 * @see https://reactrouter.com/components/outlet
943 */
944function Outlet(props) {
945 return useOutlet(props.context);
946}
947/**
948 * Declares an element that should be rendered at a certain URL path.
949 *
950 * @see https://reactrouter.com/components/route
951 */
952function Route(_props) {
953 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>.`) ;
954}
955/**
956 * Provides location context for the rest of the app.
957 *
958 * Note: You usually won't render a <Router> directly. Instead, you'll render a
959 * router that is more specific to your environment such as a <BrowserRouter>
960 * in web browsers or a <StaticRouter> for server rendering.
961 *
962 * @see https://reactrouter.com/router-components/router
963 */
964function Router({
965 basename: basenameProp = "/",
966 children = null,
967 location: locationProp,
968 navigationType = Action.Pop,
969 navigator,
970 static: staticProp = false
971}) {
972 !!useInRouterContext() ? UNSAFE_invariant(false, `You cannot render a <Router> inside another <Router>.` + ` You should never have more than one in your app.`) : void 0;
973 // Preserve trailing slashes on basename, so we can let the user control
974 // the enforcement of trailing slashes throughout the app
975 let basename = basenameProp.replace(/^\/*/, "/");
976 let navigationContext = React.useMemo(() => ({
977 basename,
978 navigator,
979 static: staticProp
980 }), [basename, navigator, staticProp]);
981 if (typeof locationProp === "string") {
982 locationProp = parsePath(locationProp);
983 }
984 let {
985 pathname = "/",
986 search = "",
987 hash = "",
988 state = null,
989 key = "default"
990 } = locationProp;
991 let locationContext = React.useMemo(() => {
992 let trailingPathname = stripBasename(pathname, basename);
993 if (trailingPathname == null) {
994 return null;
995 }
996 return {
997 location: {
998 pathname: trailingPathname,
999 search,
1000 hash,
1001 state,
1002 key
1003 },
1004 navigationType
1005 };
1006 }, [basename, pathname, search, hash, state, key, navigationType]);
1007 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.`) ;
1008 if (locationContext == null) {
1009 return null;
1010 }
1011 return /*#__PURE__*/React.createElement(NavigationContext.Provider, {
1012 value: navigationContext
1013 }, /*#__PURE__*/React.createElement(LocationContext.Provider, {
1014 children: children,
1015 value: locationContext
1016 }));
1017}
1018/**
1019 * A container for a nested tree of <Route> elements that renders the branch
1020 * that best matches the current location.
1021 *
1022 * @see https://reactrouter.com/components/routes
1023 */
1024function Routes({
1025 children,
1026 location
1027}) {
1028 return useRoutes(createRoutesFromChildren(children), location);
1029}
1030/**
1031 * Component to use for rendering lazily loaded data from returning defer()
1032 * in a loader function
1033 */
1034function Await({
1035 children,
1036 errorElement,
1037 resolve
1038}) {
1039 return /*#__PURE__*/React.createElement(AwaitErrorBoundary, {
1040 resolve: resolve,
1041 errorElement: errorElement
1042 }, /*#__PURE__*/React.createElement(ResolveAwait, null, children));
1043}
1044var AwaitRenderStatus;
1045(function (AwaitRenderStatus) {
1046 AwaitRenderStatus[AwaitRenderStatus["pending"] = 0] = "pending";
1047 AwaitRenderStatus[AwaitRenderStatus["success"] = 1] = "success";
1048 AwaitRenderStatus[AwaitRenderStatus["error"] = 2] = "error";
1049})(AwaitRenderStatus || (AwaitRenderStatus = {}));
1050const neverSettledPromise = new Promise(() => {});
1051class AwaitErrorBoundary extends React.Component {
1052 constructor(props) {
1053 super(props);
1054 this.state = {
1055 error: null
1056 };
1057 }
1058 static getDerivedStateFromError(error) {
1059 return {
1060 error
1061 };
1062 }
1063 componentDidCatch(error, errorInfo) {
1064 console.error("<Await> caught the following error during render", error, errorInfo);
1065 }
1066 render() {
1067 let {
1068 children,
1069 errorElement,
1070 resolve
1071 } = this.props;
1072 let promise = null;
1073 let status = AwaitRenderStatus.pending;
1074 if (!(resolve instanceof Promise)) {
1075 // Didn't get a promise - provide as a resolved promise
1076 status = AwaitRenderStatus.success;
1077 promise = Promise.resolve();
1078 Object.defineProperty(promise, "_tracked", {
1079 get: () => true
1080 });
1081 Object.defineProperty(promise, "_data", {
1082 get: () => resolve
1083 });
1084 } else if (this.state.error) {
1085 // Caught a render error, provide it as a rejected promise
1086 status = AwaitRenderStatus.error;
1087 let renderError = this.state.error;
1088 promise = Promise.reject().catch(() => {}); // Avoid unhandled rejection warnings
1089 Object.defineProperty(promise, "_tracked", {
1090 get: () => true
1091 });
1092 Object.defineProperty(promise, "_error", {
1093 get: () => renderError
1094 });
1095 } else if (resolve._tracked) {
1096 // Already tracked promise - check contents
1097 promise = resolve;
1098 status = promise._error !== undefined ? AwaitRenderStatus.error : promise._data !== undefined ? AwaitRenderStatus.success : AwaitRenderStatus.pending;
1099 } else {
1100 // Raw (untracked) promise - track it
1101 status = AwaitRenderStatus.pending;
1102 Object.defineProperty(resolve, "_tracked", {
1103 get: () => true
1104 });
1105 promise = resolve.then(data => Object.defineProperty(resolve, "_data", {
1106 get: () => data
1107 }), error => Object.defineProperty(resolve, "_error", {
1108 get: () => error
1109 }));
1110 }
1111 if (status === AwaitRenderStatus.error && promise._error instanceof AbortedDeferredError) {
1112 // Freeze the UI by throwing a never resolved promise
1113 throw neverSettledPromise;
1114 }
1115 if (status === AwaitRenderStatus.error && !errorElement) {
1116 // No errorElement, throw to the nearest route-level error boundary
1117 throw promise._error;
1118 }
1119 if (status === AwaitRenderStatus.error) {
1120 // Render via our errorElement
1121 return /*#__PURE__*/React.createElement(AwaitContext.Provider, {
1122 value: promise,
1123 children: errorElement
1124 });
1125 }
1126 if (status === AwaitRenderStatus.success) {
1127 // Render children with resolved value
1128 return /*#__PURE__*/React.createElement(AwaitContext.Provider, {
1129 value: promise,
1130 children: children
1131 });
1132 }
1133 // Throw to the suspense boundary
1134 throw promise;
1135 }
1136}
1137/**
1138 * @private
1139 * Indirection to leverage useAsyncValue for a render-prop API on <Await>
1140 */
1141function ResolveAwait({
1142 children
1143}) {
1144 let data = useAsyncValue();
1145 let toRender = typeof children === "function" ? children(data) : children;
1146 return /*#__PURE__*/React.createElement(React.Fragment, null, toRender);
1147}
1148///////////////////////////////////////////////////////////////////////////////
1149// UTILS
1150///////////////////////////////////////////////////////////////////////////////
1151/**
1152 * Creates a route config from a React "children" object, which is usually
1153 * either a `<Route>` element or an array of them. Used internally by
1154 * `<Routes>` to create a route config from its children.
1155 *
1156 * @see https://reactrouter.com/utils/create-routes-from-children
1157 */
1158function createRoutesFromChildren(children, parentPath = []) {
1159 let routes = [];
1160 React.Children.forEach(children, (element, index) => {
1161 if (! /*#__PURE__*/React.isValidElement(element)) {
1162 // Ignore non-elements. This allows people to more easily inline
1163 // conditionals in their route config.
1164 return;
1165 }
1166 let treePath = [...parentPath, index];
1167 if (element.type === React.Fragment) {
1168 // Transparently support React.Fragment and its children.
1169 routes.push.apply(routes, createRoutesFromChildren(element.props.children, treePath));
1170 return;
1171 }
1172 !(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;
1173 !(!element.props.index || !element.props.children) ? UNSAFE_invariant(false, "An index route cannot have child routes.") : void 0;
1174 let route = {
1175 id: element.props.id || treePath.join("-"),
1176 caseSensitive: element.props.caseSensitive,
1177 element: element.props.element,
1178 Component: element.props.Component,
1179 index: element.props.index,
1180 path: element.props.path,
1181 loader: element.props.loader,
1182 action: element.props.action,
1183 errorElement: element.props.errorElement,
1184 ErrorBoundary: element.props.ErrorBoundary,
1185 hasErrorBoundary: element.props.ErrorBoundary != null || element.props.errorElement != null,
1186 shouldRevalidate: element.props.shouldRevalidate,
1187 handle: element.props.handle,
1188 lazy: element.props.lazy
1189 };
1190 if (element.props.children) {
1191 route.children = createRoutesFromChildren(element.props.children, treePath);
1192 }
1193 routes.push(route);
1194 });
1195 return routes;
1196}
1197/**
1198 * Renders the result of `matchRoutes()` into a React element.
1199 */
1200function renderMatches(matches) {
1201 return _renderMatches(matches);
1202}
1203
1204function mapRouteProperties(route) {
1205 let updates = {
1206 // Note: this check also occurs in createRoutesFromChildren so update
1207 // there if you change this -- please and thank you!
1208 hasErrorBoundary: route.ErrorBoundary != null || route.errorElement != null
1209 };
1210 if (route.Component) {
1211 {
1212 if (route.element) {
1213 UNSAFE_warning(false, "You should not include both `Component` and `element` on your route - " + "`Component` will be used.") ;
1214 }
1215 }
1216 Object.assign(updates, {
1217 element: /*#__PURE__*/React.createElement(route.Component),
1218 Component: undefined
1219 });
1220 }
1221 if (route.ErrorBoundary) {
1222 {
1223 if (route.errorElement) {
1224 UNSAFE_warning(false, "You should not include both `ErrorBoundary` and `errorElement` on your route - " + "`ErrorBoundary` will be used.") ;
1225 }
1226 }
1227 Object.assign(updates, {
1228 errorElement: /*#__PURE__*/React.createElement(route.ErrorBoundary),
1229 ErrorBoundary: undefined
1230 });
1231 }
1232 return updates;
1233}
1234function createMemoryRouter(routes, opts) {
1235 return createRouter({
1236 basename: opts?.basename,
1237 future: {
1238 ...opts?.future,
1239 v7_prependBasename: true
1240 },
1241 history: createMemoryHistory({
1242 initialEntries: opts?.initialEntries,
1243 initialIndex: opts?.initialIndex
1244 }),
1245 hydrationData: opts?.hydrationData,
1246 routes,
1247 mapRouteProperties
1248 }).initialize();
1249}
1250
1251export { 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 };
1252//# sourceMappingURL=react-router.development.js.map