UNPKG

45.9 kBJavaScriptView Raw
1/**
2 * React Router v6.4.1
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { invariant, resolveTo, joinPaths, matchPath, warning, parsePath, matchRoutes, Action, isRouteErrorResponse, createMemoryHistory, stripBasename, AbortedDeferredError, createRouter } from '@remix-run/router';
12export { AbortedDeferredError, Action as NavigationType, createPath, defer, generatePath, isRouteErrorResponse, json, matchPath, matchRoutes, parsePath, redirect, resolvePath } from '@remix-run/router';
13import * as React from 'react';
14
15/**
16 * Copyright (c) Facebook, Inc. and its affiliates.
17 *
18 * This source code is licensed under the MIT license found in the
19 * LICENSE file in the root directory of this source tree.
20 */
21/**
22 * inlined Object.is polyfill to avoid requiring consumers ship their own
23 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
24 */
25
26function isPolyfill(x, y) {
27 return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y // eslint-disable-line no-self-compare
28 ;
29}
30
31const is = typeof Object.is === "function" ? Object.is : isPolyfill; // Intentionally not using named imports because Rollup uses dynamic
32// dispatch for CommonJS interop named imports.
33
34const {
35 useState,
36 useEffect,
37 useLayoutEffect,
38 useDebugValue
39} = React;
40let didWarnOld18Alpha = false;
41let didWarnUncachedGetSnapshot = false; // Disclaimer: This shim breaks many of the rules of React, and only works
42// because of a very particular set of implementation details and assumptions
43// -- change any one of them and it will break. The most important assumption
44// is that updates are always synchronous, because concurrent rendering is
45// only available in versions of React that also have a built-in
46// useSyncExternalStore API. And we only use this shim when the built-in API
47// does not exist.
48//
49// Do not assume that the clever hacks used by this hook also work in general.
50// The point of this shim is to replace the need for hacks by other libraries.
51
52function useSyncExternalStore$2(subscribe, getSnapshot, // Note: The shim does not use getServerSnapshot, because pre-18 versions of
53// React do not expose a way to check if we're hydrating. So users of the shim
54// will need to track that themselves and return the correct value
55// from `getSnapshot`.
56getServerSnapshot) {
57 {
58 if (!didWarnOld18Alpha) {
59 if ("startTransition" in React) {
60 didWarnOld18Alpha = true;
61 console.error("You are using an outdated, pre-release alpha of React 18 that " + "does not support useSyncExternalStore. The " + "use-sync-external-store shim will not work correctly. Upgrade " + "to a newer pre-release.");
62 }
63 }
64 } // Read the current snapshot from the store on every render. Again, this
65 // breaks the rules of React, and only works here because of specific
66 // implementation details, most importantly that updates are
67 // always synchronous.
68
69
70 const value = getSnapshot();
71
72 {
73 if (!didWarnUncachedGetSnapshot) {
74 const cachedValue = getSnapshot();
75
76 if (!is(value, cachedValue)) {
77 console.error("The result of getSnapshot should be cached to avoid an infinite loop");
78 didWarnUncachedGetSnapshot = true;
79 }
80 }
81 } // Because updates are synchronous, we don't queue them. Instead we force a
82 // re-render whenever the subscribed state changes by updating an some
83 // arbitrary useState hook. Then, during render, we call getSnapshot to read
84 // the current value.
85 //
86 // Because we don't actually use the state returned by the useState hook, we
87 // can save a bit of memory by storing other stuff in that slot.
88 //
89 // To implement the early bailout, we need to track some things on a mutable
90 // object. Usually, we would put that in a useRef hook, but we can stash it in
91 // our useState hook instead.
92 //
93 // To force a re-render, we call forceUpdate({inst}). That works because the
94 // new object always fails an equality check.
95
96
97 const [{
98 inst
99 }, forceUpdate] = useState({
100 inst: {
101 value,
102 getSnapshot
103 }
104 }); // Track the latest getSnapshot function with a ref. This needs to be updated
105 // in the layout phase so we can access it during the tearing check that
106 // happens on subscribe.
107
108 useLayoutEffect(() => {
109 inst.value = value;
110 inst.getSnapshot = getSnapshot; // Whenever getSnapshot or subscribe changes, we need to check in the
111 // commit phase if there was an interleaved mutation. In concurrent mode
112 // this can happen all the time, but even in synchronous mode, an earlier
113 // effect may have mutated the store.
114
115 if (checkIfSnapshotChanged(inst)) {
116 // Force a re-render.
117 forceUpdate({
118 inst
119 });
120 } // eslint-disable-next-line react-hooks/exhaustive-deps
121
122 }, [subscribe, value, getSnapshot]);
123 useEffect(() => {
124 // Check for changes right before subscribing. Subsequent changes will be
125 // detected in the subscription handler.
126 if (checkIfSnapshotChanged(inst)) {
127 // Force a re-render.
128 forceUpdate({
129 inst
130 });
131 }
132
133 const handleStoreChange = () => {
134 // TODO: Because there is no cross-renderer API for batching updates, it's
135 // up to the consumer of this library to wrap their subscription event
136 // with unstable_batchedUpdates. Should we try to detect when this isn't
137 // the case and print a warning in development?
138 // The store changed. Check if the snapshot changed since the last time we
139 // read from the store.
140 if (checkIfSnapshotChanged(inst)) {
141 // Force a re-render.
142 forceUpdate({
143 inst
144 });
145 }
146 }; // Subscribe to the store and return a clean-up function.
147
148
149 return subscribe(handleStoreChange); // eslint-disable-next-line react-hooks/exhaustive-deps
150 }, [subscribe]);
151 useDebugValue(value);
152 return value;
153}
154
155function checkIfSnapshotChanged(inst) {
156 const latestGetSnapshot = inst.getSnapshot;
157 const prevValue = inst.value;
158
159 try {
160 const nextValue = latestGetSnapshot();
161 return !is(prevValue, nextValue);
162 } catch (error) {
163 return true;
164 }
165}
166
167/**
168 * Copyright (c) Facebook, Inc. and its affiliates.
169 *
170 * This source code is licensed under the MIT license found in the
171 * LICENSE file in the root directory of this source tree.
172 *
173 * @flow
174 */
175function useSyncExternalStore$1(subscribe, getSnapshot, getServerSnapshot) {
176 // Note: The shim does not use getServerSnapshot, because pre-18 versions of
177 // React do not expose a way to check if we're hydrating. So users of the shim
178 // will need to track that themselves and return the correct value
179 // from `getSnapshot`.
180 return getSnapshot();
181}
182
183/**
184 * Inlined into the react-router repo since use-sync-external-store does not
185 * provide a UMD-compatible package, so we need this to be able to distribute
186 * UMD react-router bundles
187 */
188const canUseDOM = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined");
189const isServerEnvironment = !canUseDOM;
190const shim = isServerEnvironment ? useSyncExternalStore$1 : useSyncExternalStore$2;
191const useSyncExternalStore = "useSyncExternalStore" in React ? (module => module.useSyncExternalStore)(React) : shim;
192
193const DataStaticRouterContext = /*#__PURE__*/React.createContext(null);
194
195{
196 DataStaticRouterContext.displayName = "DataStaticRouterContext";
197}
198
199const DataRouterContext = /*#__PURE__*/React.createContext(null);
200
201{
202 DataRouterContext.displayName = "DataRouter";
203}
204
205const DataRouterStateContext = /*#__PURE__*/React.createContext(null);
206
207{
208 DataRouterStateContext.displayName = "DataRouterState";
209}
210
211const AwaitContext = /*#__PURE__*/React.createContext(null);
212
213{
214 AwaitContext.displayName = "Await";
215}
216
217const NavigationContext = /*#__PURE__*/React.createContext(null);
218
219{
220 NavigationContext.displayName = "Navigation";
221}
222
223const LocationContext = /*#__PURE__*/React.createContext(null);
224
225{
226 LocationContext.displayName = "Location";
227}
228
229const RouteContext = /*#__PURE__*/React.createContext({
230 outlet: null,
231 matches: []
232});
233
234{
235 RouteContext.displayName = "Route";
236}
237
238const RouteErrorContext = /*#__PURE__*/React.createContext(null);
239
240{
241 RouteErrorContext.displayName = "RouteError";
242}
243
244/**
245 * Returns the full href for the given "to" value. This is useful for building
246 * custom links that are also accessible and preserve right-click behavior.
247 *
248 * @see https://reactrouter.com/docs/en/v6/hooks/use-href
249 */
250
251function useHref(to, {
252 relative
253} = {}) {
254 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
255 // router loaded. We can help them understand how to avoid that.
256 `useHref() may be used only in the context of a <Router> component.`) : void 0;
257 let {
258 basename,
259 navigator
260 } = React.useContext(NavigationContext);
261 let {
262 hash,
263 pathname,
264 search
265 } = useResolvedPath(to, {
266 relative
267 });
268 let joinedPathname = pathname; // If we're operating within a basename, prepend it to the pathname prior
269 // to creating the href. If this is a root navigation, then just use the raw
270 // basename which allows the basename to have full control over the presence
271 // of a trailing slash on root links
272
273 if (basename !== "/") {
274 joinedPathname = pathname === "/" ? basename : joinPaths([basename, pathname]);
275 }
276
277 return navigator.createHref({
278 pathname: joinedPathname,
279 search,
280 hash
281 });
282}
283/**
284 * Returns true if this component is a descendant of a <Router>.
285 *
286 * @see https://reactrouter.com/docs/en/v6/hooks/use-in-router-context
287 */
288
289function useInRouterContext() {
290 return React.useContext(LocationContext) != null;
291}
292/**
293 * Returns the current location object, which represents the current URL in web
294 * browsers.
295 *
296 * Note: If you're using this it may mean you're doing some of your own
297 * "routing" in your app, and we'd like to know what your use case is. We may
298 * be able to provide something higher-level to better suit your needs.
299 *
300 * @see https://reactrouter.com/docs/en/v6/hooks/use-location
301 */
302
303function useLocation() {
304 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
305 // router loaded. We can help them understand how to avoid that.
306 `useLocation() may be used only in the context of a <Router> component.`) : void 0;
307 return React.useContext(LocationContext).location;
308}
309/**
310 * Returns the current navigation action which describes how the router came to
311 * the current location, either by a pop, push, or replace on the history stack.
312 *
313 * @see https://reactrouter.com/docs/en/v6/hooks/use-navigation-type
314 */
315
316function useNavigationType() {
317 return React.useContext(LocationContext).navigationType;
318}
319/**
320 * Returns true if the URL for the given "to" value matches the current URL.
321 * This is useful for components that need to know "active" state, e.g.
322 * <NavLink>.
323 *
324 * @see https://reactrouter.com/docs/en/v6/hooks/use-match
325 */
326
327function useMatch(pattern) {
328 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
329 // router loaded. We can help them understand how to avoid that.
330 `useMatch() may be used only in the context of a <Router> component.`) : void 0;
331 let {
332 pathname
333 } = useLocation();
334 return React.useMemo(() => matchPath(pattern, pathname), [pathname, pattern]);
335}
336/**
337 * When processing relative navigation we want to ignore ancestor routes that
338 * do not contribute to the path, such that index/pathless layout routes don't
339 * interfere.
340 *
341 * For example, when moving a route element into an index route and/or a
342 * pathless layout route, relative link behavior contained within should stay
343 * the same. Both of the following examples should link back to the root:
344 *
345 * <Route path="/">
346 * <Route path="accounts" element={<Link to=".."}>
347 * </Route>
348 *
349 * <Route path="/">
350 * <Route path="accounts">
351 * <Route element={<AccountsLayout />}> // <-- Does not contribute
352 * <Route index element={<Link to=".."} /> // <-- Does not contribute
353 * </Route
354 * </Route>
355 * </Route>
356 */
357
358function getPathContributingMatches(matches) {
359 return matches.filter((match, index) => index === 0 || !match.route.index && match.pathnameBase !== matches[index - 1].pathnameBase);
360}
361/**
362 * Returns an imperative method for changing the location. Used by <Link>s, but
363 * may also be used by other elements to change the location.
364 *
365 * @see https://reactrouter.com/docs/en/v6/hooks/use-navigate
366 */
367
368
369function useNavigate() {
370 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
371 // router loaded. We can help them understand how to avoid that.
372 `useNavigate() may be used only in the context of a <Router> component.`) : void 0;
373 let {
374 basename,
375 navigator
376 } = React.useContext(NavigationContext);
377 let {
378 matches
379 } = React.useContext(RouteContext);
380 let {
381 pathname: locationPathname
382 } = useLocation();
383 let routePathnamesJson = JSON.stringify(getPathContributingMatches(matches).map(match => match.pathnameBase));
384 let activeRef = React.useRef(false);
385 React.useEffect(() => {
386 activeRef.current = true;
387 });
388 let navigate = React.useCallback((to, options = {}) => {
389 warning(activeRef.current, `You should call navigate() in a React.useEffect(), not when ` + `your component is first rendered.`) ;
390 if (!activeRef.current) return;
391
392 if (typeof to === "number") {
393 navigator.go(to);
394 return;
395 }
396
397 let path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, options.relative === "path"); // If we're operating within a basename, prepend it to the pathname prior
398 // to handing off to history. If this is a root navigation, then we
399 // navigate to the raw basename which allows the basename to have full
400 // control over the presence of a trailing slash on root links
401
402 if (basename !== "/") {
403 path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
404 }
405
406 (!!options.replace ? navigator.replace : navigator.push)(path, options.state, options);
407 }, [basename, navigator, routePathnamesJson, locationPathname]);
408 return navigate;
409}
410const OutletContext = /*#__PURE__*/React.createContext(null);
411/**
412 * Returns the context (if provided) for the child route at this level of the route
413 * hierarchy.
414 * @see https://reactrouter.com/docs/en/v6/hooks/use-outlet-context
415 */
416
417function useOutletContext() {
418 return React.useContext(OutletContext);
419}
420/**
421 * Returns the element for the child route at this level of the route
422 * hierarchy. Used internally by <Outlet> to render child routes.
423 *
424 * @see https://reactrouter.com/docs/en/v6/hooks/use-outlet
425 */
426
427function useOutlet(context) {
428 let outlet = React.useContext(RouteContext).outlet;
429
430 if (outlet) {
431 return /*#__PURE__*/React.createElement(OutletContext.Provider, {
432 value: context
433 }, outlet);
434 }
435
436 return outlet;
437}
438/**
439 * Returns an object of key/value pairs of the dynamic params from the current
440 * URL that were matched by the route path.
441 *
442 * @see https://reactrouter.com/docs/en/v6/hooks/use-params
443 */
444
445function useParams() {
446 let {
447 matches
448 } = React.useContext(RouteContext);
449 let routeMatch = matches[matches.length - 1];
450 return routeMatch ? routeMatch.params : {};
451}
452/**
453 * Resolves the pathname of the given `to` value against the current location.
454 *
455 * @see https://reactrouter.com/docs/en/v6/hooks/use-resolved-path
456 */
457
458function useResolvedPath(to, {
459 relative
460} = {}) {
461 let {
462 matches
463 } = React.useContext(RouteContext);
464 let {
465 pathname: locationPathname
466 } = useLocation();
467 let routePathnamesJson = JSON.stringify(getPathContributingMatches(matches).map(match => match.pathnameBase));
468 return React.useMemo(() => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, relative === "path"), [to, routePathnamesJson, locationPathname, relative]);
469}
470/**
471 * Returns the element of the route that matched the current location, prepared
472 * with the correct context to render the remainder of the route tree. Route
473 * elements in the tree must render an <Outlet> to render their child route's
474 * element.
475 *
476 * @see https://reactrouter.com/docs/en/v6/hooks/use-routes
477 */
478
479function useRoutes(routes, locationArg) {
480 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
481 // router loaded. We can help them understand how to avoid that.
482 `useRoutes() may be used only in the context of a <Router> component.`) : void 0;
483 let dataRouterStateContext = React.useContext(DataRouterStateContext);
484 let {
485 matches: parentMatches
486 } = React.useContext(RouteContext);
487 let routeMatch = parentMatches[parentMatches.length - 1];
488 let parentParams = routeMatch ? routeMatch.params : {};
489 let parentPathname = routeMatch ? routeMatch.pathname : "/";
490 let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/";
491 let parentRoute = routeMatch && routeMatch.route;
492
493 {
494 // You won't get a warning about 2 different <Routes> under a <Route>
495 // without a trailing *, but this is a best-effort warning anyway since we
496 // cannot even give the warning unless they land at the parent route.
497 //
498 // Example:
499 //
500 // <Routes>
501 // {/* This route path MUST end with /* because otherwise
502 // it will never match /blog/post/123 */}
503 // <Route path="blog" element={<Blog />} />
504 // <Route path="blog/feed" element={<BlogFeed />} />
505 // </Routes>
506 //
507 // function Blog() {
508 // return (
509 // <Routes>
510 // <Route path="post/:id" element={<Post />} />
511 // </Routes>
512 // );
513 // }
514 let parentPath = parentRoute && parentRoute.path || "";
515 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}/*`}">.`);
516 }
517
518 let locationFromContext = useLocation();
519 let location;
520
521 if (locationArg) {
522 let parsedLocationArg = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
523 !(parentPathnameBase === "/" || parsedLocationArg.pathname?.startsWith(parentPathnameBase)) ? 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;
524 location = parsedLocationArg;
525 } else {
526 location = locationFromContext;
527 }
528
529 let pathname = location.pathname || "/";
530 let remainingPathname = parentPathnameBase === "/" ? pathname : pathname.slice(parentPathnameBase.length) || "/";
531 let matches = matchRoutes(routes, {
532 pathname: remainingPathname
533 });
534
535 {
536 warning(parentRoute || matches != null, `No routes matched location "${location.pathname}${location.search}${location.hash}" `) ;
537 warning(matches == null || matches[matches.length - 1].route.element !== undefined, `Matched leaf route at location "${location.pathname}${location.search}${location.hash}" does not have an element. ` + `This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.`) ;
538 }
539
540 let renderedMatches = _renderMatches(matches && matches.map(match => Object.assign({}, match, {
541 params: Object.assign({}, parentParams, match.params),
542 pathname: joinPaths([parentPathnameBase, match.pathname]),
543 pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase, match.pathnameBase])
544 })), parentMatches, dataRouterStateContext || undefined); // When a user passes in a `locationArg`, the associated routes need to
545 // be wrapped in a new `LocationContext.Provider` in order for `useLocation`
546 // to use the scoped location instead of the global location.
547
548
549 if (locationArg) {
550 return /*#__PURE__*/React.createElement(LocationContext.Provider, {
551 value: {
552 location: {
553 pathname: "/",
554 search: "",
555 hash: "",
556 state: null,
557 key: "default",
558 ...location
559 },
560 navigationType: Action.Pop
561 }
562 }, renderedMatches);
563 }
564
565 return renderedMatches;
566}
567
568function DefaultErrorElement() {
569 let error = useRouteError();
570 let message = isRouteErrorResponse(error) ? `${error.status} ${error.statusText}` : error instanceof Error ? error.message : JSON.stringify(error);
571 let stack = error instanceof Error ? error.stack : null;
572 let lightgrey = "rgba(200,200,200, 0.5)";
573 let preStyles = {
574 padding: "0.5rem",
575 backgroundColor: lightgrey
576 };
577 let codeStyles = {
578 padding: "2px 4px",
579 backgroundColor: lightgrey
580 };
581 return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h2", null, "Unhandled Thrown Error!"), /*#__PURE__*/React.createElement("h3", {
582 style: {
583 fontStyle: "italic"
584 }
585 }, message), stack ? /*#__PURE__*/React.createElement("pre", {
586 style: preStyles
587 }, stack) : 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\u00A0", /*#__PURE__*/React.createElement("code", {
588 style: codeStyles
589 }, "errorElement"), " props on\u00A0", /*#__PURE__*/React.createElement("code", {
590 style: codeStyles
591 }, "<Route>")));
592}
593
594class RenderErrorBoundary extends React.Component {
595 constructor(props) {
596 super(props);
597 this.state = {
598 location: props.location,
599 error: props.error
600 };
601 }
602
603 static getDerivedStateFromError(error) {
604 return {
605 error: error
606 };
607 }
608
609 static getDerivedStateFromProps(props, state) {
610 // When we get into an error state, the user will likely click "back" to the
611 // previous page that didn't have an error. Because this wraps the entire
612 // application, that will have no effect--the error page continues to display.
613 // This gives us a mechanism to recover from the error when the location changes.
614 //
615 // Whether we're in an error state or not, we update the location in state
616 // so that when we are in an error state, it gets reset when a new location
617 // comes in and the user recovers from the error.
618 if (state.location !== props.location) {
619 return {
620 error: props.error,
621 location: props.location
622 };
623 } // If we're not changing locations, preserve the location but still surface
624 // any new errors that may come through. We retain the existing error, we do
625 // this because the error provided from the app state may be cleared without
626 // the location changing.
627
628
629 return {
630 error: props.error || state.error,
631 location: state.location
632 };
633 }
634
635 componentDidCatch(error, errorInfo) {
636 console.error("React Router caught the following error during render", error, errorInfo);
637 }
638
639 render() {
640 return this.state.error ? /*#__PURE__*/React.createElement(RouteErrorContext.Provider, {
641 value: this.state.error,
642 children: this.props.component
643 }) : this.props.children;
644 }
645
646}
647
648function RenderedRoute({
649 routeContext,
650 match,
651 children
652}) {
653 let dataStaticRouterContext = React.useContext(DataStaticRouterContext); // Track how deep we got in our render pass to emulate SSR componentDidCatch
654 // in a DataStaticRouter
655
656 if (dataStaticRouterContext && match.route.errorElement) {
657 dataStaticRouterContext._deepestRenderedBoundaryId = match.route.id;
658 }
659
660 return /*#__PURE__*/React.createElement(RouteContext.Provider, {
661 value: routeContext
662 }, children);
663}
664
665function _renderMatches(matches, parentMatches = [], dataRouterState) {
666 if (matches == null) {
667 if (dataRouterState?.errors) {
668 // Don't bail if we have data router errors so we can render them in the
669 // boundary. Use the pre-matched (or shimmed) matches
670 matches = dataRouterState.matches;
671 } else {
672 return null;
673 }
674 }
675
676 let renderedMatches = matches; // If we have data errors, trim matches to the highest error boundary
677
678 let errors = dataRouterState?.errors;
679
680 if (errors != null) {
681 let errorIndex = renderedMatches.findIndex(m => m.route.id && errors?.[m.route.id]);
682 !(errorIndex >= 0) ? invariant(false, `Could not find a matching route for the current errors: ${errors}`) : void 0;
683 renderedMatches = renderedMatches.slice(0, Math.min(renderedMatches.length, errorIndex + 1));
684 }
685
686 return renderedMatches.reduceRight((outlet, match, index) => {
687 let error = match.route.id ? errors?.[match.route.id] : null; // Only data routers handle errors
688
689 let errorElement = dataRouterState ? match.route.errorElement || /*#__PURE__*/React.createElement(DefaultErrorElement, null) : null;
690
691 let getChildren = () => /*#__PURE__*/React.createElement(RenderedRoute, {
692 match: match,
693 routeContext: {
694 outlet,
695 matches: parentMatches.concat(renderedMatches.slice(0, index + 1))
696 }
697 }, error ? errorElement : match.route.element !== undefined ? match.route.element : outlet); // Only wrap in an error boundary within data router usages when we have an
698 // errorElement on this route. Otherwise let it bubble up to an ancestor
699 // errorElement
700
701
702 return dataRouterState && (match.route.errorElement || index === 0) ? /*#__PURE__*/React.createElement(RenderErrorBoundary, {
703 location: dataRouterState.location,
704 component: errorElement,
705 error: error,
706 children: getChildren()
707 }) : getChildren();
708 }, null);
709}
710var DataRouterHook;
711
712(function (DataRouterHook) {
713 DataRouterHook["UseLoaderData"] = "useLoaderData";
714 DataRouterHook["UseActionData"] = "useActionData";
715 DataRouterHook["UseRouteError"] = "useRouteError";
716 DataRouterHook["UseNavigation"] = "useNavigation";
717 DataRouterHook["UseRouteLoaderData"] = "useRouteLoaderData";
718 DataRouterHook["UseMatches"] = "useMatches";
719 DataRouterHook["UseRevalidator"] = "useRevalidator";
720})(DataRouterHook || (DataRouterHook = {}));
721
722function useDataRouterState(hookName) {
723 let state = React.useContext(DataRouterStateContext);
724 !state ? invariant(false, `${hookName} must be used within a DataRouterStateContext`) : void 0;
725 return state;
726}
727/**
728 * Returns the current navigation, defaulting to an "idle" navigation when
729 * no navigation is in progress
730 */
731
732
733function useNavigation() {
734 let state = useDataRouterState(DataRouterHook.UseNavigation);
735 return state.navigation;
736}
737/**
738 * Returns a revalidate function for manually triggering revalidation, as well
739 * as the current state of any manual revalidations
740 */
741
742function useRevalidator() {
743 let dataRouterContext = React.useContext(DataRouterContext);
744 !dataRouterContext ? invariant(false, `useRevalidator must be used within a DataRouterContext`) : void 0;
745 let state = useDataRouterState(DataRouterHook.UseRevalidator);
746 return {
747 revalidate: dataRouterContext.router.revalidate,
748 state: state.revalidation
749 };
750}
751/**
752 * Returns the active route matches, useful for accessing loaderData for
753 * parent/child routes or the route "handle" property
754 */
755
756function useMatches() {
757 let {
758 matches,
759 loaderData
760 } = useDataRouterState(DataRouterHook.UseMatches);
761 return React.useMemo(() => matches.map(match => {
762 let {
763 pathname,
764 params
765 } = match; // Note: This structure matches that created by createUseMatchesMatch
766 // in the @remix-run/router , so if you change this please also change
767 // that :) Eventually we'll DRY this up
768
769 return {
770 id: match.route.id,
771 pathname,
772 params,
773 data: loaderData[match.route.id],
774 handle: match.route.handle
775 };
776 }), [matches, loaderData]);
777}
778/**
779 * Returns the loader data for the nearest ancestor Route loader
780 */
781
782function useLoaderData() {
783 let state = useDataRouterState(DataRouterHook.UseLoaderData);
784 let route = React.useContext(RouteContext);
785 !route ? invariant(false, `useLoaderData must be used inside a RouteContext`) : void 0;
786 let thisRoute = route.matches[route.matches.length - 1];
787 !thisRoute.route.id ? invariant(false, `useLoaderData can only be used on routes that contain a unique "id"`) : void 0;
788 return state.loaderData[thisRoute.route.id];
789}
790/**
791 * Returns the loaderData for the given routeId
792 */
793
794function useRouteLoaderData(routeId) {
795 let state = useDataRouterState(DataRouterHook.UseRouteLoaderData);
796 return state.loaderData[routeId];
797}
798/**
799 * Returns the action data for the nearest ancestor Route action
800 */
801
802function useActionData() {
803 let state = useDataRouterState(DataRouterHook.UseActionData);
804 let route = React.useContext(RouteContext);
805 !route ? invariant(false, `useActionData must be used inside a RouteContext`) : void 0;
806 return Object.values(state?.actionData || {})[0];
807}
808/**
809 * Returns the nearest ancestor Route error, which could be a loader/action
810 * error or a render error. This is intended to be called from your
811 * errorElement to display a proper error message.
812 */
813
814function useRouteError() {
815 let error = React.useContext(RouteErrorContext);
816 let state = useDataRouterState(DataRouterHook.UseRouteError);
817 let route = React.useContext(RouteContext);
818 let thisRoute = route.matches[route.matches.length - 1]; // If this was a render error, we put it in a RouteError context inside
819 // of RenderErrorBoundary
820
821 if (error) {
822 return error;
823 }
824
825 !route ? invariant(false, `useRouteError must be used inside a RouteContext`) : void 0;
826 !thisRoute.route.id ? invariant(false, `useRouteError can only be used on routes that contain a unique "id"`) : void 0; // Otherwise look for errors from our data router state
827
828 return state.errors?.[thisRoute.route.id];
829}
830/**
831 * Returns the happy-path data from the nearest ancestor <Await /> value
832 */
833
834function useAsyncValue() {
835 let value = React.useContext(AwaitContext);
836 return value?._data;
837}
838/**
839 * Returns the error from the nearest ancestor <Await /> value
840 */
841
842function useAsyncError() {
843 let value = React.useContext(AwaitContext);
844 return value?._error;
845}
846const alreadyWarned = {};
847
848function warningOnce(key, cond, message) {
849 if (!cond && !alreadyWarned[key]) {
850 alreadyWarned[key] = true;
851 warning(false, message) ;
852 }
853}
854
855/**
856 * Given a Remix Router instance, render the appropriate UI
857 */
858
859function RouterProvider({
860 fallbackElement,
861 router
862}) {
863 // Sync router state to our component state to force re-renders
864 let state = useSyncExternalStore(router.subscribe, () => router.state, // We have to provide this so React@18 doesn't complain during hydration,
865 // but we pass our serialized hydration data into the router so state here
866 // is already synced with what the server saw
867 () => router.state);
868 let navigator = React.useMemo(() => {
869 return {
870 createHref: router.createHref,
871 go: n => router.navigate(n),
872 push: (to, state, opts) => router.navigate(to, {
873 state,
874 preventScrollReset: opts?.preventScrollReset
875 }),
876 replace: (to, state, opts) => router.navigate(to, {
877 replace: true,
878 state,
879 preventScrollReset: opts?.preventScrollReset
880 })
881 };
882 }, [router]);
883 let basename = router.basename || "/";
884 return /*#__PURE__*/React.createElement(DataRouterContext.Provider, {
885 value: {
886 router,
887 navigator,
888 static: false,
889 // Do we need this?
890 basename
891 }
892 }, /*#__PURE__*/React.createElement(DataRouterStateContext.Provider, {
893 value: state
894 }, /*#__PURE__*/React.createElement(Router, {
895 basename: router.basename,
896 location: router.state.location,
897 navigationType: router.state.historyAction,
898 navigator: navigator
899 }, router.state.initialized ? /*#__PURE__*/React.createElement(Routes, null) : fallbackElement)));
900}
901/**
902 * A <Router> that stores all entries in memory.
903 *
904 * @see https://reactrouter.com/docs/en/v6/routers/memory-router
905 */
906
907function MemoryRouter({
908 basename,
909 children,
910 initialEntries,
911 initialIndex
912}) {
913 let historyRef = React.useRef();
914
915 if (historyRef.current == null) {
916 historyRef.current = createMemoryHistory({
917 initialEntries,
918 initialIndex,
919 v5Compat: true
920 });
921 }
922
923 let history = historyRef.current;
924 let [state, setState] = React.useState({
925 action: history.action,
926 location: history.location
927 });
928 React.useLayoutEffect(() => history.listen(setState), [history]);
929 return /*#__PURE__*/React.createElement(Router, {
930 basename: basename,
931 children: children,
932 location: state.location,
933 navigationType: state.action,
934 navigator: history
935 });
936}
937/**
938 * Changes the current location.
939 *
940 * Note: This API is mostly useful in React.Component subclasses that are not
941 * able to use hooks. In functional components, we recommend you use the
942 * `useNavigate` hook instead.
943 *
944 * @see https://reactrouter.com/docs/en/v6/components/navigate
945 */
946
947function Navigate({
948 to,
949 replace,
950 state,
951 relative
952}) {
953 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of
954 // the router loaded. We can help them understand how to avoid that.
955 `<Navigate> may be used only in the context of a <Router> component.`) : void 0;
956 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.`) ;
957 let dataRouterState = React.useContext(DataRouterStateContext);
958 let navigate = useNavigate();
959 React.useEffect(() => {
960 // Avoid kicking off multiple navigations if we're in the middle of a
961 // data-router navigation, since components get re-rendered when we enter
962 // a submitting/loading state
963 if (dataRouterState && dataRouterState.navigation.state !== "idle") {
964 return;
965 }
966
967 navigate(to, {
968 replace,
969 state,
970 relative
971 });
972 });
973 return null;
974}
975/**
976 * Renders the child route's element, if there is one.
977 *
978 * @see https://reactrouter.com/docs/en/v6/components/outlet
979 */
980
981function Outlet(props) {
982 return useOutlet(props.context);
983}
984/**
985 * Declares an element that should be rendered at a certain URL path.
986 *
987 * @see https://reactrouter.com/docs/en/v6/components/route
988 */
989
990function Route(_props) {
991 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>.`) ;
992}
993/**
994 * Provides location context for the rest of the app.
995 *
996 * Note: You usually won't render a <Router> directly. Instead, you'll render a
997 * router that is more specific to your environment such as a <BrowserRouter>
998 * in web browsers or a <StaticRouter> for server rendering.
999 *
1000 * @see https://reactrouter.com/docs/en/v6/routers/router
1001 */
1002
1003function Router({
1004 basename: basenameProp = "/",
1005 children = null,
1006 location: locationProp,
1007 navigationType = Action.Pop,
1008 navigator,
1009 static: staticProp = false
1010}) {
1011 !!useInRouterContext() ? invariant(false, `You cannot render a <Router> inside another <Router>.` + ` You should never have more than one in your app.`) : void 0; // Preserve trailing slashes on basename, so we can let the user control
1012 // the enforcement of trailing slashes throughout the app
1013
1014 let basename = basenameProp.replace(/^\/*/, "/");
1015 let navigationContext = React.useMemo(() => ({
1016 basename,
1017 navigator,
1018 static: staticProp
1019 }), [basename, navigator, staticProp]);
1020
1021 if (typeof locationProp === "string") {
1022 locationProp = parsePath(locationProp);
1023 }
1024
1025 let {
1026 pathname = "/",
1027 search = "",
1028 hash = "",
1029 state = null,
1030 key = "default"
1031 } = locationProp;
1032 let location = React.useMemo(() => {
1033 let trailingPathname = stripBasename(pathname, basename);
1034
1035 if (trailingPathname == null) {
1036 return null;
1037 }
1038
1039 return {
1040 pathname: trailingPathname,
1041 search,
1042 hash,
1043 state,
1044 key
1045 };
1046 }, [basename, pathname, search, hash, state, key]);
1047 warning(location != 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.`) ;
1048
1049 if (location == null) {
1050 return null;
1051 }
1052
1053 return /*#__PURE__*/React.createElement(NavigationContext.Provider, {
1054 value: navigationContext
1055 }, /*#__PURE__*/React.createElement(LocationContext.Provider, {
1056 children: children,
1057 value: {
1058 location,
1059 navigationType
1060 }
1061 }));
1062}
1063/**
1064 * A container for a nested tree of <Route> elements that renders the branch
1065 * that best matches the current location.
1066 *
1067 * @see https://reactrouter.com/docs/en/v6/components/routes
1068 */
1069
1070function Routes({
1071 children,
1072 location
1073}) {
1074 let dataRouterContext = React.useContext(DataRouterContext); // When in a DataRouterContext _without_ children, we use the router routes
1075 // directly. If we have children, then we're in a descendant tree and we
1076 // need to use child routes.
1077
1078 let routes = dataRouterContext && !children ? dataRouterContext.router.routes : createRoutesFromChildren(children);
1079 return useRoutes(routes, location);
1080}
1081/**
1082 * Component to use for rendering lazily loaded data from returning defer()
1083 * in a loader function
1084 */
1085
1086function Await({
1087 children,
1088 errorElement,
1089 resolve
1090}) {
1091 return /*#__PURE__*/React.createElement(AwaitErrorBoundary, {
1092 resolve: resolve,
1093 errorElement: errorElement
1094 }, /*#__PURE__*/React.createElement(ResolveAwait, null, children));
1095}
1096var AwaitRenderStatus;
1097
1098(function (AwaitRenderStatus) {
1099 AwaitRenderStatus[AwaitRenderStatus["pending"] = 0] = "pending";
1100 AwaitRenderStatus[AwaitRenderStatus["success"] = 1] = "success";
1101 AwaitRenderStatus[AwaitRenderStatus["error"] = 2] = "error";
1102})(AwaitRenderStatus || (AwaitRenderStatus = {}));
1103
1104const neverSettledPromise = new Promise(() => {});
1105
1106class AwaitErrorBoundary extends React.Component {
1107 constructor(props) {
1108 super(props);
1109 this.state = {
1110 error: null
1111 };
1112 }
1113
1114 static getDerivedStateFromError(error) {
1115 return {
1116 error
1117 };
1118 }
1119
1120 componentDidCatch(error, errorInfo) {
1121 console.error("<Await> caught the following error during render", error, errorInfo);
1122 }
1123
1124 render() {
1125 let {
1126 children,
1127 errorElement,
1128 resolve
1129 } = this.props;
1130 let promise = null;
1131 let status = AwaitRenderStatus.pending;
1132
1133 if (!(resolve instanceof Promise)) {
1134 // Didn't get a promise - provide as a resolved promise
1135 status = AwaitRenderStatus.success;
1136 promise = Promise.resolve();
1137 Object.defineProperty(promise, "_tracked", {
1138 get: () => true
1139 });
1140 Object.defineProperty(promise, "_data", {
1141 get: () => resolve
1142 });
1143 } else if (this.state.error) {
1144 // Caught a render error, provide it as a rejected promise
1145 status = AwaitRenderStatus.error;
1146 let renderError = this.state.error;
1147 promise = Promise.reject().catch(() => {}); // Avoid unhandled rejection warnings
1148
1149 Object.defineProperty(promise, "_tracked", {
1150 get: () => true
1151 });
1152 Object.defineProperty(promise, "_error", {
1153 get: () => renderError
1154 });
1155 } else if (resolve._tracked) {
1156 // Already tracked promise - check contents
1157 promise = resolve;
1158 status = promise._error !== undefined ? AwaitRenderStatus.error : promise._data !== undefined ? AwaitRenderStatus.success : AwaitRenderStatus.pending;
1159 } else {
1160 // Raw (untracked) promise - track it
1161 status = AwaitRenderStatus.pending;
1162 Object.defineProperty(resolve, "_tracked", {
1163 get: () => true
1164 });
1165 promise = resolve.then(data => Object.defineProperty(resolve, "_data", {
1166 get: () => data
1167 }), error => Object.defineProperty(resolve, "_error", {
1168 get: () => error
1169 }));
1170 }
1171
1172 if (status === AwaitRenderStatus.error && promise._error instanceof AbortedDeferredError) {
1173 // Freeze the UI by throwing a never resolved promise
1174 throw neverSettledPromise;
1175 }
1176
1177 if (status === AwaitRenderStatus.error && !errorElement) {
1178 // No errorElement, throw to the nearest route-level error boundary
1179 throw promise._error;
1180 }
1181
1182 if (status === AwaitRenderStatus.error) {
1183 // Render via our errorElement
1184 return /*#__PURE__*/React.createElement(AwaitContext.Provider, {
1185 value: promise,
1186 children: errorElement
1187 });
1188 }
1189
1190 if (status === AwaitRenderStatus.success) {
1191 // Render children with resolved value
1192 return /*#__PURE__*/React.createElement(AwaitContext.Provider, {
1193 value: promise,
1194 children: children
1195 });
1196 } // Throw to the suspense boundary
1197
1198
1199 throw promise;
1200 }
1201
1202}
1203/**
1204 * @private
1205 * Indirection to leverage useAsyncValue for a render-prop API on <Await>
1206 */
1207
1208
1209function ResolveAwait({
1210 children
1211}) {
1212 let data = useAsyncValue();
1213
1214 if (typeof children === "function") {
1215 return children(data);
1216 }
1217
1218 return /*#__PURE__*/React.createElement(React.Fragment, null, children);
1219} ///////////////////////////////////////////////////////////////////////////////
1220// UTILS
1221///////////////////////////////////////////////////////////////////////////////
1222
1223/**
1224 * Creates a route config from a React "children" object, which is usually
1225 * either a `<Route>` element or an array of them. Used internally by
1226 * `<Routes>` to create a route config from its children.
1227 *
1228 * @see https://reactrouter.com/docs/en/v6/utils/create-routes-from-children
1229 */
1230
1231
1232function createRoutesFromChildren(children, parentPath = []) {
1233 let routes = [];
1234 React.Children.forEach(children, (element, index) => {
1235 if (! /*#__PURE__*/React.isValidElement(element)) {
1236 // Ignore non-elements. This allows people to more easily inline
1237 // conditionals in their route config.
1238 return;
1239 }
1240
1241 if (element.type === React.Fragment) {
1242 // Transparently support React.Fragment and its children.
1243 routes.push.apply(routes, createRoutesFromChildren(element.props.children, parentPath));
1244 return;
1245 }
1246
1247 !(element.type === Route) ? 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;
1248 let treePath = [...parentPath, index];
1249 let route = {
1250 id: element.props.id || treePath.join("-"),
1251 caseSensitive: element.props.caseSensitive,
1252 element: element.props.element,
1253 index: element.props.index,
1254 path: element.props.path,
1255 loader: element.props.loader,
1256 action: element.props.action,
1257 errorElement: element.props.errorElement,
1258 hasErrorBoundary: element.props.errorElement != null,
1259 shouldRevalidate: element.props.shouldRevalidate,
1260 handle: element.props.handle
1261 };
1262
1263 if (element.props.children) {
1264 route.children = createRoutesFromChildren(element.props.children, treePath);
1265 }
1266
1267 routes.push(route);
1268 });
1269 return routes;
1270}
1271/**
1272 * Renders the result of `matchRoutes()` into a React element.
1273 */
1274
1275function renderMatches(matches) {
1276 return _renderMatches(matches);
1277}
1278/**
1279 * @private
1280 * Walk the route tree and add hasErrorBoundary if it's not provided, so that
1281 * users providing manual route arrays can just specify errorElement
1282 */
1283
1284function enhanceManualRouteObjects(routes) {
1285 return routes.map(route => {
1286 let routeClone = { ...route
1287 };
1288
1289 if (routeClone.hasErrorBoundary == null) {
1290 routeClone.hasErrorBoundary = routeClone.errorElement != null;
1291 }
1292
1293 if (routeClone.children) {
1294 routeClone.children = enhanceManualRouteObjects(routeClone.children);
1295 }
1296
1297 return routeClone;
1298 });
1299}
1300
1301function createMemoryRouter(routes, opts) {
1302 return createRouter({
1303 basename: opts?.basename,
1304 history: createMemoryHistory({
1305 initialEntries: opts?.initialEntries,
1306 initialIndex: opts?.initialIndex
1307 }),
1308 hydrationData: opts?.hydrationData,
1309 routes: enhanceManualRouteObjects(routes)
1310 }).initialize();
1311} ///////////////////////////////////////////////////////////////////////////////
1312
1313export { Await, MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, DataRouterContext as UNSAFE_DataRouterContext, DataRouterStateContext as UNSAFE_DataRouterStateContext, DataStaticRouterContext as UNSAFE_DataStaticRouterContext, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, enhanceManualRouteObjects as UNSAFE_enhanceManualRouteObjects, createMemoryRouter, createRoutesFromChildren, createRoutesFromChildren as createRoutesFromElements, renderMatches, useActionData, useAsyncError, useAsyncValue, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes };
1314//# sourceMappingURL=react-router.development.js.map