UNPKG

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