UNPKG

31.3 kBJavaScriptView Raw
1/**
2 * React Router v6.3.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 { parsePath, createMemoryHistory, Action } from 'history';
12export { Action as NavigationType, createPath, parsePath } from 'history';
13import { createContext, useContext, useMemo, useRef, useEffect, useCallback, createElement, useState, useLayoutEffect, Children, isValidElement, Fragment } from 'react';
14
15const NavigationContext = /*#__PURE__*/createContext(null);
16
17{
18 NavigationContext.displayName = "Navigation";
19}
20
21const LocationContext = /*#__PURE__*/createContext(null);
22
23{
24 LocationContext.displayName = "Location";
25}
26
27const RouteContext = /*#__PURE__*/createContext({
28 outlet: null,
29 matches: []
30});
31
32{
33 RouteContext.displayName = "Route";
34}
35
36function invariant(cond, message) {
37 if (!cond) throw new Error(message);
38}
39function warning(cond, message) {
40 if (!cond) {
41 // eslint-disable-next-line no-console
42 if (typeof console !== "undefined") console.warn(message);
43
44 try {
45 // Welcome to debugging React Router!
46 //
47 // This error is thrown as a convenience so you can more easily
48 // find the source for a warning that appears in the console by
49 // enabling "pause on exceptions" in your JavaScript debugger.
50 throw new Error(message); // eslint-disable-next-line no-empty
51 } catch (e) {}
52 }
53}
54const alreadyWarned = {};
55function warningOnce(key, cond, message) {
56 if (!cond && !alreadyWarned[key]) {
57 alreadyWarned[key] = true;
58 warning(false, message) ;
59 }
60}
61
62/**
63 * Returns a path with params interpolated.
64 *
65 * @see https://reactrouter.com/docs/en/v6/api#generatepath
66 */
67function generatePath(path, params = {}) {
68 return path.replace(/:(\w+)/g, (_, key) => {
69 !(params[key] != null) ? invariant(false, `Missing ":${key}" param`) : void 0;
70 return params[key];
71 }).replace(/\/*\*$/, _ => params["*"] == null ? "" : params["*"].replace(/^\/*/, "/"));
72}
73/**
74 * A RouteMatch contains info about how a route matched a URL.
75 */
76
77/**
78 * Matches the given routes to a location and returns the match data.
79 *
80 * @see https://reactrouter.com/docs/en/v6/api#matchroutes
81 */
82function matchRoutes(routes, locationArg, basename = "/") {
83 let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
84 let pathname = stripBasename(location.pathname || "/", basename);
85
86 if (pathname == null) {
87 return null;
88 }
89
90 let branches = flattenRoutes(routes);
91 rankRouteBranches(branches);
92 let matches = null;
93
94 for (let i = 0; matches == null && i < branches.length; ++i) {
95 matches = matchRouteBranch(branches[i], pathname);
96 }
97
98 return matches;
99}
100
101function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
102 routes.forEach((route, index) => {
103 let meta = {
104 relativePath: route.path || "",
105 caseSensitive: route.caseSensitive === true,
106 childrenIndex: index,
107 route
108 };
109
110 if (meta.relativePath.startsWith("/")) {
111 !meta.relativePath.startsWith(parentPath) ? invariant(false, `Absolute route path "${meta.relativePath}" nested under path ` + `"${parentPath}" is not valid. An absolute child route path ` + `must start with the combined path of all its parent routes.`) : void 0;
112 meta.relativePath = meta.relativePath.slice(parentPath.length);
113 }
114
115 let path = joinPaths([parentPath, meta.relativePath]);
116 let routesMeta = parentsMeta.concat(meta); // Add the children before adding this route to the array so we traverse the
117 // route tree depth-first and child routes appear before their parents in
118 // the "flattened" version.
119
120 if (route.children && route.children.length > 0) {
121 !(route.index !== true) ? invariant(false, `Index routes must not have child routes. Please remove ` + `all child routes from route path "${path}".`) : void 0;
122 flattenRoutes(route.children, branches, routesMeta, path);
123 } // Routes without a path shouldn't ever match by themselves unless they are
124 // index routes, so don't add them to the list of possible branches.
125
126
127 if (route.path == null && !route.index) {
128 return;
129 }
130
131 branches.push({
132 path,
133 score: computeScore(path, route.index),
134 routesMeta
135 });
136 });
137 return branches;
138}
139
140function rankRouteBranches(branches) {
141 branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
142 : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
143}
144
145const paramRe = /^:\w+$/;
146const dynamicSegmentValue = 3;
147const indexRouteValue = 2;
148const emptySegmentValue = 1;
149const staticSegmentValue = 10;
150const splatPenalty = -2;
151
152const isSplat = s => s === "*";
153
154function computeScore(path, index) {
155 let segments = path.split("/");
156 let initialScore = segments.length;
157
158 if (segments.some(isSplat)) {
159 initialScore += splatPenalty;
160 }
161
162 if (index) {
163 initialScore += indexRouteValue;
164 }
165
166 return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
167}
168
169function compareIndexes(a, b) {
170 let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
171 return siblings ? // If two routes are siblings, we should try to match the earlier sibling
172 // first. This allows people to have fine-grained control over the matching
173 // behavior by simply putting routes with identical paths in the order they
174 // want them tried.
175 a[a.length - 1] - b[b.length - 1] : // Otherwise, it doesn't really make sense to rank non-siblings by index,
176 // so they sort equally.
177 0;
178}
179
180function matchRouteBranch(branch, pathname) {
181 let {
182 routesMeta
183 } = branch;
184 let matchedParams = {};
185 let matchedPathname = "/";
186 let matches = [];
187
188 for (let i = 0; i < routesMeta.length; ++i) {
189 let meta = routesMeta[i];
190 let end = i === routesMeta.length - 1;
191 let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
192 let match = matchPath({
193 path: meta.relativePath,
194 caseSensitive: meta.caseSensitive,
195 end
196 }, remainingPathname);
197 if (!match) return null;
198 Object.assign(matchedParams, match.params);
199 let route = meta.route;
200 matches.push({
201 params: matchedParams,
202 pathname: joinPaths([matchedPathname, match.pathname]),
203 pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
204 route
205 });
206
207 if (match.pathnameBase !== "/") {
208 matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
209 }
210 }
211
212 return matches;
213}
214/**
215 * A PathPattern is used to match on some portion of a URL pathname.
216 */
217
218
219/**
220 * Performs pattern matching on a URL pathname and returns information about
221 * the match.
222 *
223 * @see https://reactrouter.com/docs/en/v6/api#matchpath
224 */
225function matchPath(pattern, pathname) {
226 if (typeof pattern === "string") {
227 pattern = {
228 path: pattern,
229 caseSensitive: false,
230 end: true
231 };
232 }
233
234 let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
235 let match = pathname.match(matcher);
236 if (!match) return null;
237 let matchedPathname = match[0];
238 let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
239 let captureGroups = match.slice(1);
240 let params = paramNames.reduce((memo, paramName, index) => {
241 // We need to compute the pathnameBase here using the raw splat value
242 // instead of using params["*"] later because it will be decoded then
243 if (paramName === "*") {
244 let splatValue = captureGroups[index] || "";
245 pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
246 }
247
248 memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
249 return memo;
250 }, {});
251 return {
252 params,
253 pathname: matchedPathname,
254 pathnameBase,
255 pattern
256 };
257}
258
259function compilePath(path, caseSensitive = false, end = true) {
260 warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), `Route path "${path}" will be treated as if it were ` + `"${path.replace(/\*$/, "/*")}" because the \`*\` character must ` + `always follow a \`/\` in the pattern. To get rid of this warning, ` + `please change the route path to "${path.replace(/\*$/, "/*")}".`) ;
261 let paramNames = [];
262 let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
263 .replace(/^\/*/, "/") // Make sure it has a leading /
264 .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
265 .replace(/:(\w+)/g, (_, paramName) => {
266 paramNames.push(paramName);
267 return "([^\\/]+)";
268 });
269
270 if (path.endsWith("*")) {
271 paramNames.push("*");
272 regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
273 : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
274 } else {
275 regexpSource += end ? "\\/*$" // When matching to the end, ignore trailing slashes
276 : // Otherwise, match a word boundary or a proceeding /. The word boundary restricts
277 // parent routes to matching only their own words and nothing more, e.g. parent
278 // route "/home" should not match "/home2".
279 // Additionally, allow paths starting with `.`, `-`, `~`, and url-encoded entities,
280 // but do not consume the character in the matched path so they can match against
281 // nested paths.
282 "(?:(?=[.~-]|%[0-9A-F]{2})|\\b|\\/|$)";
283 }
284
285 let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
286 return [matcher, paramNames];
287}
288
289function safelyDecodeURIComponent(value, paramName) {
290 try {
291 return decodeURIComponent(value);
292 } catch (error) {
293 warning(false, `The value for the URL param "${paramName}" will not be decoded because` + ` the string "${value}" is a malformed URL segment. This is probably` + ` due to a bad percent encoding (${error}).`) ;
294 return value;
295 }
296}
297/**
298 * Returns a resolved path object relative to the given pathname.
299 *
300 * @see https://reactrouter.com/docs/en/v6/api#resolvepath
301 */
302
303
304function resolvePath(to, fromPathname = "/") {
305 let {
306 pathname: toPathname,
307 search = "",
308 hash = ""
309 } = typeof to === "string" ? parsePath(to) : to;
310 let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
311 return {
312 pathname,
313 search: normalizeSearch(search),
314 hash: normalizeHash(hash)
315 };
316}
317
318function resolvePathname(relativePath, fromPathname) {
319 let segments = fromPathname.replace(/\/+$/, "").split("/");
320 let relativeSegments = relativePath.split("/");
321 relativeSegments.forEach(segment => {
322 if (segment === "..") {
323 // Keep the root "" segment so the pathname starts at /
324 if (segments.length > 1) segments.pop();
325 } else if (segment !== ".") {
326 segments.push(segment);
327 }
328 });
329 return segments.length > 1 ? segments.join("/") : "/";
330}
331
332function resolveTo(toArg, routePathnames, locationPathname) {
333 let to = typeof toArg === "string" ? parsePath(toArg) : toArg;
334 let toPathname = toArg === "" || to.pathname === "" ? "/" : to.pathname; // If a pathname is explicitly provided in `to`, it should be relative to the
335 // route context. This is explained in `Note on `<Link to>` values` in our
336 // migration guide from v5 as a means of disambiguation between `to` values
337 // that begin with `/` and those that do not. However, this is problematic for
338 // `to` values that do not provide a pathname. `to` can simply be a search or
339 // hash string, in which case we should assume that the navigation is relative
340 // to the current location's pathname and *not* the route pathname.
341
342 let from;
343
344 if (toPathname == null) {
345 from = locationPathname;
346 } else {
347 let routePathnameIndex = routePathnames.length - 1;
348
349 if (toPathname.startsWith("..")) {
350 let toSegments = toPathname.split("/"); // Each leading .. segment means "go up one route" instead of "go up one
351 // URL segment". This is a key difference from how <a href> works and a
352 // major reason we call this a "to" value instead of a "href".
353
354 while (toSegments[0] === "..") {
355 toSegments.shift();
356 routePathnameIndex -= 1;
357 }
358
359 to.pathname = toSegments.join("/");
360 } // If there are more ".." segments than parent routes, resolve relative to
361 // the root / URL.
362
363
364 from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
365 }
366
367 let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original to value had one.
368
369 if (toPathname && toPathname !== "/" && toPathname.endsWith("/") && !path.pathname.endsWith("/")) {
370 path.pathname += "/";
371 }
372
373 return path;
374}
375function getToPathname(to) {
376 // Empty strings should be treated the same as / paths
377 return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
378}
379function stripBasename(pathname, basename) {
380 if (basename === "/") return pathname;
381
382 if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
383 return null;
384 }
385
386 let nextChar = pathname.charAt(basename.length);
387
388 if (nextChar && nextChar !== "/") {
389 // pathname does not start with basename/
390 return null;
391 }
392
393 return pathname.slice(basename.length) || "/";
394}
395const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
396const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
397
398const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
399
400const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
401
402/**
403 * Returns the full href for the given "to" value. This is useful for building
404 * custom links that are also accessible and preserve right-click behavior.
405 *
406 * @see https://reactrouter.com/docs/en/v6/api#usehref
407 */
408
409function useHref(to) {
410 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
411 // router loaded. We can help them understand how to avoid that.
412 `useHref() may be used only in the context of a <Router> component.`) : void 0;
413 let {
414 basename,
415 navigator
416 } = useContext(NavigationContext);
417 let {
418 hash,
419 pathname,
420 search
421 } = useResolvedPath(to);
422 let joinedPathname = pathname;
423
424 if (basename !== "/") {
425 let toPathname = getToPathname(to);
426 let endsWithSlash = toPathname != null && toPathname.endsWith("/");
427 joinedPathname = pathname === "/" ? basename + (endsWithSlash ? "/" : "") : joinPaths([basename, pathname]);
428 }
429
430 return navigator.createHref({
431 pathname: joinedPathname,
432 search,
433 hash
434 });
435}
436/**
437 * Returns true if this component is a descendant of a <Router>.
438 *
439 * @see https://reactrouter.com/docs/en/v6/api#useinroutercontext
440 */
441
442function useInRouterContext() {
443 return useContext(LocationContext) != null;
444}
445/**
446 * Returns the current location object, which represents the current URL in web
447 * browsers.
448 *
449 * Note: If you're using this it may mean you're doing some of your own
450 * "routing" in your app, and we'd like to know what your use case is. We may
451 * be able to provide something higher-level to better suit your needs.
452 *
453 * @see https://reactrouter.com/docs/en/v6/api#uselocation
454 */
455
456function useLocation() {
457 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
458 // router loaded. We can help them understand how to avoid that.
459 `useLocation() may be used only in the context of a <Router> component.`) : void 0;
460 return useContext(LocationContext).location;
461}
462/**
463 * Returns the current navigation action which describes how the router came to
464 * the current location, either by a pop, push, or replace on the history stack.
465 *
466 * @see https://reactrouter.com/docs/en/v6/api#usenavigationtype
467 */
468
469function useNavigationType() {
470 return useContext(LocationContext).navigationType;
471}
472/**
473 * Returns true if the URL for the given "to" value matches the current URL.
474 * This is useful for components that need to know "active" state, e.g.
475 * <NavLink>.
476 *
477 * @see https://reactrouter.com/docs/en/v6/api#usematch
478 */
479
480function useMatch(pattern) {
481 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
482 // router loaded. We can help them understand how to avoid that.
483 `useMatch() may be used only in the context of a <Router> component.`) : void 0;
484 let {
485 pathname
486 } = useLocation();
487 return useMemo(() => matchPath(pattern, pathname), [pathname, pattern]);
488}
489/**
490 * The interface for the navigate() function returned from useNavigate().
491 */
492
493/**
494 * Returns an imperative method for changing the location. Used by <Link>s, but
495 * may also be used by other elements to change the location.
496 *
497 * @see https://reactrouter.com/docs/en/v6/api#usenavigate
498 */
499function useNavigate() {
500 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
501 // router loaded. We can help them understand how to avoid that.
502 `useNavigate() may be used only in the context of a <Router> component.`) : void 0;
503 let {
504 basename,
505 navigator
506 } = useContext(NavigationContext);
507 let {
508 matches
509 } = useContext(RouteContext);
510 let {
511 pathname: locationPathname
512 } = useLocation();
513 let routePathnamesJson = JSON.stringify(matches.map(match => match.pathnameBase));
514 let activeRef = useRef(false);
515 useEffect(() => {
516 activeRef.current = true;
517 });
518 let navigate = useCallback((to, options = {}) => {
519 warning(activeRef.current, `You should call navigate() in a React.useEffect(), not when ` + `your component is first rendered.`) ;
520 if (!activeRef.current) return;
521
522 if (typeof to === "number") {
523 navigator.go(to);
524 return;
525 }
526
527 let path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname);
528
529 if (basename !== "/") {
530 path.pathname = joinPaths([basename, path.pathname]);
531 }
532
533 (!!options.replace ? navigator.replace : navigator.push)(path, options.state);
534 }, [basename, navigator, routePathnamesJson, locationPathname]);
535 return navigate;
536}
537const OutletContext = /*#__PURE__*/createContext(null);
538/**
539 * Returns the context (if provided) for the child route at this level of the route
540 * hierarchy.
541 * @see https://reactrouter.com/docs/en/v6/api#useoutletcontext
542 */
543
544function useOutletContext() {
545 return useContext(OutletContext);
546}
547/**
548 * Returns the element for the child route at this level of the route
549 * hierarchy. Used internally by <Outlet> to render child routes.
550 *
551 * @see https://reactrouter.com/docs/en/v6/api#useoutlet
552 */
553
554function useOutlet(context) {
555 let outlet = useContext(RouteContext).outlet;
556
557 if (outlet) {
558 return /*#__PURE__*/createElement(OutletContext.Provider, {
559 value: context
560 }, outlet);
561 }
562
563 return outlet;
564}
565/**
566 * Returns an object of key/value pairs of the dynamic params from the current
567 * URL that were matched by the route path.
568 *
569 * @see https://reactrouter.com/docs/en/v6/api#useparams
570 */
571
572function useParams() {
573 let {
574 matches
575 } = useContext(RouteContext);
576 let routeMatch = matches[matches.length - 1];
577 return routeMatch ? routeMatch.params : {};
578}
579/**
580 * Resolves the pathname of the given `to` value against the current location.
581 *
582 * @see https://reactrouter.com/docs/en/v6/api#useresolvedpath
583 */
584
585function useResolvedPath(to) {
586 let {
587 matches
588 } = useContext(RouteContext);
589 let {
590 pathname: locationPathname
591 } = useLocation();
592 let routePathnamesJson = JSON.stringify(matches.map(match => match.pathnameBase));
593 return useMemo(() => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname), [to, routePathnamesJson, locationPathname]);
594}
595/**
596 * Returns the element of the route that matched the current location, prepared
597 * with the correct context to render the remainder of the route tree. Route
598 * elements in the tree must render an <Outlet> to render their child route's
599 * element.
600 *
601 * @see https://reactrouter.com/docs/en/v6/api#useroutes
602 */
603
604function useRoutes(routes, locationArg) {
605 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
606 // router loaded. We can help them understand how to avoid that.
607 `useRoutes() may be used only in the context of a <Router> component.`) : void 0;
608 let {
609 matches: parentMatches
610 } = useContext(RouteContext);
611 let routeMatch = parentMatches[parentMatches.length - 1];
612 let parentParams = routeMatch ? routeMatch.params : {};
613 let parentPathname = routeMatch ? routeMatch.pathname : "/";
614 let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/";
615 let parentRoute = routeMatch && routeMatch.route;
616
617 {
618 // You won't get a warning about 2 different <Routes> under a <Route>
619 // without a trailing *, but this is a best-effort warning anyway since we
620 // cannot even give the warning unless they land at the parent route.
621 //
622 // Example:
623 //
624 // <Routes>
625 // {/* This route path MUST end with /* because otherwise
626 // it will never match /blog/post/123 */}
627 // <Route path="blog" element={<Blog />} />
628 // <Route path="blog/feed" element={<BlogFeed />} />
629 // </Routes>
630 //
631 // function Blog() {
632 // return (
633 // <Routes>
634 // <Route path="post/:id" element={<Post />} />
635 // </Routes>
636 // );
637 // }
638 let parentPath = parentRoute && parentRoute.path || "";
639 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}/*`}">.`);
640 }
641
642 let locationFromContext = useLocation();
643 let location;
644
645 if (locationArg) {
646 let parsedLocationArg = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
647 !(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;
648 location = parsedLocationArg;
649 } else {
650 location = locationFromContext;
651 }
652
653 let pathname = location.pathname || "/";
654 let remainingPathname = parentPathnameBase === "/" ? pathname : pathname.slice(parentPathnameBase.length) || "/";
655 let matches = matchRoutes(routes, {
656 pathname: remainingPathname
657 });
658
659 {
660 warning(parentRoute || matches != null, `No routes matched location "${location.pathname}${location.search}${location.hash}" `) ;
661 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.`) ;
662 }
663
664 return _renderMatches(matches && matches.map(match => Object.assign({}, match, {
665 params: Object.assign({}, parentParams, match.params),
666 pathname: joinPaths([parentPathnameBase, match.pathname]),
667 pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase, match.pathnameBase])
668 })), parentMatches);
669}
670function _renderMatches(matches, parentMatches = []) {
671 if (matches == null) return null;
672 return matches.reduceRight((outlet, match, index) => {
673 return /*#__PURE__*/createElement(RouteContext.Provider, {
674 children: match.route.element !== undefined ? match.route.element : outlet,
675 value: {
676 outlet,
677 matches: parentMatches.concat(matches.slice(0, index + 1))
678 }
679 });
680 }, null);
681}
682
683/**
684 * A <Router> that stores all entries in memory.
685 *
686 * @see https://reactrouter.com/docs/en/v6/api#memoryrouter
687 */
688function MemoryRouter({
689 basename,
690 children,
691 initialEntries,
692 initialIndex
693}) {
694 let historyRef = useRef();
695
696 if (historyRef.current == null) {
697 historyRef.current = createMemoryHistory({
698 initialEntries,
699 initialIndex
700 });
701 }
702
703 let history = historyRef.current;
704 let [state, setState] = useState({
705 action: history.action,
706 location: history.location
707 });
708 useLayoutEffect(() => history.listen(setState), [history]);
709 return /*#__PURE__*/createElement(Router, {
710 basename: basename,
711 children: children,
712 location: state.location,
713 navigationType: state.action,
714 navigator: history
715 });
716}
717
718/**
719 * Changes the current location.
720 *
721 * Note: This API is mostly useful in React.Component subclasses that are not
722 * able to use hooks. In functional components, we recommend you use the
723 * `useNavigate` hook instead.
724 *
725 * @see https://reactrouter.com/docs/en/v6/api#navigate
726 */
727function Navigate({
728 to,
729 replace,
730 state
731}) {
732 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of
733 // the router loaded. We can help them understand how to avoid that.
734 `<Navigate> may be used only in the context of a <Router> component.`) : void 0;
735 warning(!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.`) ;
736 let navigate = useNavigate();
737 useEffect(() => {
738 navigate(to, {
739 replace,
740 state
741 });
742 });
743 return null;
744}
745
746/**
747 * Renders the child route's element, if there is one.
748 *
749 * @see https://reactrouter.com/docs/en/v6/api#outlet
750 */
751function Outlet(props) {
752 return useOutlet(props.context);
753}
754
755/**
756 * Declares an element that should be rendered at a certain URL path.
757 *
758 * @see https://reactrouter.com/docs/en/v6/api#route
759 */
760function Route(_props) {
761 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>.`) ;
762}
763
764/**
765 * Provides location context for the rest of the app.
766 *
767 * Note: You usually won't render a <Router> directly. Instead, you'll render a
768 * router that is more specific to your environment such as a <BrowserRouter>
769 * in web browsers or a <StaticRouter> for server rendering.
770 *
771 * @see https://reactrouter.com/docs/en/v6/api#router
772 */
773function Router({
774 basename: basenameProp = "/",
775 children = null,
776 location: locationProp,
777 navigationType = Action.Pop,
778 navigator,
779 static: staticProp = false
780}) {
781 !!useInRouterContext() ? invariant(false, `You cannot render a <Router> inside another <Router>.` + ` You should never have more than one in your app.`) : void 0;
782 let basename = normalizePathname(basenameProp);
783 let navigationContext = useMemo(() => ({
784 basename,
785 navigator,
786 static: staticProp
787 }), [basename, navigator, staticProp]);
788
789 if (typeof locationProp === "string") {
790 locationProp = parsePath(locationProp);
791 }
792
793 let {
794 pathname = "/",
795 search = "",
796 hash = "",
797 state = null,
798 key = "default"
799 } = locationProp;
800 let location = useMemo(() => {
801 let trailingPathname = stripBasename(pathname, basename);
802
803 if (trailingPathname == null) {
804 return null;
805 }
806
807 return {
808 pathname: trailingPathname,
809 search,
810 hash,
811 state,
812 key
813 };
814 }, [basename, pathname, search, hash, state, key]);
815 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.`) ;
816
817 if (location == null) {
818 return null;
819 }
820
821 return /*#__PURE__*/createElement(NavigationContext.Provider, {
822 value: navigationContext
823 }, /*#__PURE__*/createElement(LocationContext.Provider, {
824 children: children,
825 value: {
826 location,
827 navigationType
828 }
829 }));
830}
831
832/**
833 * A container for a nested tree of <Route> elements that renders the branch
834 * that best matches the current location.
835 *
836 * @see https://reactrouter.com/docs/en/v6/api#routes
837 */
838function Routes({
839 children,
840 location
841}) {
842 return useRoutes(createRoutesFromChildren(children), location);
843} ///////////////////////////////////////////////////////////////////////////////
844// UTILS
845///////////////////////////////////////////////////////////////////////////////
846
847/**
848 * Creates a route config from a React "children" object, which is usually
849 * either a `<Route>` element or an array of them. Used internally by
850 * `<Routes>` to create a route config from its children.
851 *
852 * @see https://reactrouter.com/docs/en/v6/api#createroutesfromchildren
853 */
854
855function createRoutesFromChildren(children) {
856 let routes = [];
857 Children.forEach(children, element => {
858 if (! /*#__PURE__*/isValidElement(element)) {
859 // Ignore non-elements. This allows people to more easily inline
860 // conditionals in their route config.
861 return;
862 }
863
864 if (element.type === Fragment) {
865 // Transparently support React.Fragment and its children.
866 routes.push.apply(routes, createRoutesFromChildren(element.props.children));
867 return;
868 }
869
870 !(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;
871 let route = {
872 caseSensitive: element.props.caseSensitive,
873 element: element.props.element,
874 index: element.props.index,
875 path: element.props.path
876 };
877
878 if (element.props.children) {
879 route.children = createRoutesFromChildren(element.props.children);
880 }
881
882 routes.push(route);
883 });
884 return routes;
885}
886/**
887 * Renders the result of `matchRoutes()` into a React element.
888 */
889
890function renderMatches(matches) {
891 return _renderMatches(matches);
892}
893
894export { MemoryRouter, Navigate, Outlet, Route, Router, Routes, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, createRoutesFromChildren, generatePath, matchPath, matchRoutes, renderMatches, resolvePath, useHref, useInRouterContext, useLocation, useMatch, useNavigate, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes };
895//# sourceMappingURL=react-router.development.js.map