/** * React Router v6.8.1 * * Copyright (c) Remix Software Inc. * * This source code is licensed under the MIT license found in the * LICENSE.md file in the root directory of this source tree. * * @license MIT */ import { invariant, joinPaths, matchPath, UNSAFE_getPathContributingMatches, warning, resolveTo, parsePath, matchRoutes, Action, isRouteErrorResponse, createMemoryHistory, stripBasename, AbortedDeferredError, createRouter } from '@remix-run/router'; export { AbortedDeferredError, Action as NavigationType, createPath, defer, generatePath, isRouteErrorResponse, json, matchPath, matchRoutes, parsePath, redirect, resolvePath } from '@remix-run/router'; import * as React from 'react'; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * inlined Object.is polyfill to avoid requiring consumers ship their own * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is */ function isPolyfill(x, y) { return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y // eslint-disable-line no-self-compare ; } const is = typeof Object.is === "function" ? Object.is : isPolyfill; // Intentionally not using named imports because Rollup uses dynamic // dispatch for CommonJS interop named imports. const { useState, useEffect, useLayoutEffect, useDebugValue } = React; let didWarnOld18Alpha = false; let didWarnUncachedGetSnapshot = false; // Disclaimer: This shim breaks many of the rules of React, and only works // because of a very particular set of implementation details and assumptions // -- change any one of them and it will break. The most important assumption // is that updates are always synchronous, because concurrent rendering is // only available in versions of React that also have a built-in // useSyncExternalStore API. And we only use this shim when the built-in API // does not exist. // // Do not assume that the clever hacks used by this hook also work in general. // The point of this shim is to replace the need for hacks by other libraries. function useSyncExternalStore$2(subscribe, getSnapshot, // Note: The shim does not use getServerSnapshot, because pre-18 versions of // React do not expose a way to check if we're hydrating. So users of the shim // will need to track that themselves and return the correct value // from `getSnapshot`. getServerSnapshot) { { if (!didWarnOld18Alpha) { if ("startTransition" in React) { didWarnOld18Alpha = true; 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."); } } } // Read the current snapshot from the store on every render. Again, this // breaks the rules of React, and only works here because of specific // implementation details, most importantly that updates are // always synchronous. const value = getSnapshot(); { if (!didWarnUncachedGetSnapshot) { const cachedValue = getSnapshot(); if (!is(value, cachedValue)) { console.error("The result of getSnapshot should be cached to avoid an infinite loop"); didWarnUncachedGetSnapshot = true; } } } // Because updates are synchronous, we don't queue them. Instead we force a // re-render whenever the subscribed state changes by updating an some // arbitrary useState hook. Then, during render, we call getSnapshot to read // the current value. // // Because we don't actually use the state returned by the useState hook, we // can save a bit of memory by storing other stuff in that slot. // // To implement the early bailout, we need to track some things on a mutable // object. Usually, we would put that in a useRef hook, but we can stash it in // our useState hook instead. // // To force a re-render, we call forceUpdate({inst}). That works because the // new object always fails an equality check. const [{ inst }, forceUpdate] = useState({ inst: { value, getSnapshot } }); // Track the latest getSnapshot function with a ref. This needs to be updated // in the layout phase so we can access it during the tearing check that // happens on subscribe. useLayoutEffect(() => { inst.value = value; inst.getSnapshot = getSnapshot; // Whenever getSnapshot or subscribe changes, we need to check in the // commit phase if there was an interleaved mutation. In concurrent mode // this can happen all the time, but even in synchronous mode, an earlier // effect may have mutated the store. if (checkIfSnapshotChanged(inst)) { // Force a re-render. forceUpdate({ inst }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [subscribe, value, getSnapshot]); useEffect(() => { // Check for changes right before subscribing. Subsequent changes will be // detected in the subscription handler. if (checkIfSnapshotChanged(inst)) { // Force a re-render. forceUpdate({ inst }); } const handleStoreChange = () => { // TODO: Because there is no cross-renderer API for batching updates, it's // up to the consumer of this library to wrap their subscription event // with unstable_batchedUpdates. Should we try to detect when this isn't // the case and print a warning in development? // The store changed. Check if the snapshot changed since the last time we // read from the store. if (checkIfSnapshotChanged(inst)) { // Force a re-render. forceUpdate({ inst }); } }; // Subscribe to the store and return a clean-up function. return subscribe(handleStoreChange); // eslint-disable-next-line react-hooks/exhaustive-deps }, [subscribe]); useDebugValue(value); return value; } function checkIfSnapshotChanged(inst) { const latestGetSnapshot = inst.getSnapshot; const prevValue = inst.value; try { const nextValue = latestGetSnapshot(); return !is(prevValue, nextValue); } catch (error) { return true; } } /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ function useSyncExternalStore$1(subscribe, getSnapshot, getServerSnapshot) { // Note: The shim does not use getServerSnapshot, because pre-18 versions of // React do not expose a way to check if we're hydrating. So users of the shim // will need to track that themselves and return the correct value // from `getSnapshot`. return getSnapshot(); } /** * Inlined into the react-router repo since use-sync-external-store does not * provide a UMD-compatible package, so we need this to be able to distribute * UMD react-router bundles */ const canUseDOM = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined"); const isServerEnvironment = !canUseDOM; const shim = isServerEnvironment ? useSyncExternalStore$1 : useSyncExternalStore$2; const useSyncExternalStore = "useSyncExternalStore" in React ? (module => module.useSyncExternalStore)(React) : shim; const DataRouterContext = /*#__PURE__*/React.createContext(null); { DataRouterContext.displayName = "DataRouter"; } const DataRouterStateContext = /*#__PURE__*/React.createContext(null); { DataRouterStateContext.displayName = "DataRouterState"; } const AwaitContext = /*#__PURE__*/React.createContext(null); { AwaitContext.displayName = "Await"; } const NavigationContext = /*#__PURE__*/React.createContext(null); { NavigationContext.displayName = "Navigation"; } const LocationContext = /*#__PURE__*/React.createContext(null); { LocationContext.displayName = "Location"; } const RouteContext = /*#__PURE__*/React.createContext({ outlet: null, matches: [] }); { RouteContext.displayName = "Route"; } const RouteErrorContext = /*#__PURE__*/React.createContext(null); { RouteErrorContext.displayName = "RouteError"; } /** * Returns the full href for the given "to" value. This is useful for building * custom links that are also accessible and preserve right-click behavior. * * @see https://reactrouter.com/hooks/use-href */ function useHref(to, { relative } = {}) { !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useHref() may be used only in the context of a component.`) : void 0; let { basename, navigator } = React.useContext(NavigationContext); let { hash, pathname, search } = useResolvedPath(to, { relative }); let joinedPathname = pathname; // If we're operating within a basename, prepend it to the pathname prior // to creating the href. If this is a root navigation, then just use the raw // basename which allows the basename to have full control over the presence // of a trailing slash on root links if (basename !== "/") { joinedPathname = pathname === "/" ? basename : joinPaths([basename, pathname]); } return navigator.createHref({ pathname: joinedPathname, search, hash }); } /** * Returns true if this component is a descendant of a . * * @see https://reactrouter.com/hooks/use-in-router-context */ function useInRouterContext() { return React.useContext(LocationContext) != null; } /** * Returns the current location object, which represents the current URL in web * browsers. * * Note: If you're using this it may mean you're doing some of your own * "routing" in your app, and we'd like to know what your use case is. We may * be able to provide something higher-level to better suit your needs. * * @see https://reactrouter.com/hooks/use-location */ function useLocation() { !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useLocation() may be used only in the context of a component.`) : void 0; return React.useContext(LocationContext).location; } /** * Returns the current navigation action which describes how the router came to * the current location, either by a pop, push, or replace on the history stack. * * @see https://reactrouter.com/hooks/use-navigation-type */ function useNavigationType() { return React.useContext(LocationContext).navigationType; } /** * Returns a PathMatch object if the given pattern matches the current URL. * This is useful for components that need to know "active" state, e.g. * . * * @see https://reactrouter.com/hooks/use-match */ function useMatch(pattern) { !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useMatch() may be used only in the context of a component.`) : void 0; let { pathname } = useLocation(); return React.useMemo(() => matchPath(pattern, pathname), [pathname, pattern]); } /** * Returns an imperative method for changing the location. Used by s, but * may also be used by other elements to change the location. * * @see https://reactrouter.com/hooks/use-navigate */ function useNavigate() { !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useNavigate() may be used only in the context of a component.`) : void 0; let { basename, navigator } = React.useContext(NavigationContext); let { matches } = React.useContext(RouteContext); let { pathname: locationPathname } = useLocation(); let routePathnamesJson = JSON.stringify(UNSAFE_getPathContributingMatches(matches).map(match => match.pathnameBase)); let activeRef = React.useRef(false); React.useEffect(() => { activeRef.current = true; }); let navigate = React.useCallback((to, options = {}) => { warning(activeRef.current, `You should call navigate() in a React.useEffect(), not when ` + `your component is first rendered.`) ; if (!activeRef.current) return; if (typeof to === "number") { navigator.go(to); return; } let path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, options.relative === "path"); // If we're operating within a basename, prepend it to the pathname prior // to handing off to history. If this is a root navigation, then we // navigate to the raw basename which allows the basename to have full // control over the presence of a trailing slash on root links if (basename !== "/") { path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]); } (!!options.replace ? navigator.replace : navigator.push)(path, options.state, options); }, [basename, navigator, routePathnamesJson, locationPathname]); return navigate; } const OutletContext = /*#__PURE__*/React.createContext(null); /** * Returns the context (if provided) for the child route at this level of the route * hierarchy. * @see https://reactrouter.com/hooks/use-outlet-context */ function useOutletContext() { return React.useContext(OutletContext); } /** * Returns the element for the child route at this level of the route * hierarchy. Used internally by to render child routes. * * @see https://reactrouter.com/hooks/use-outlet */ function useOutlet(context) { let outlet = React.useContext(RouteContext).outlet; if (outlet) { return /*#__PURE__*/React.createElement(OutletContext.Provider, { value: context }, outlet); } return outlet; } /** * Returns an object of key/value pairs of the dynamic params from the current * URL that were matched by the route path. * * @see https://reactrouter.com/hooks/use-params */ function useParams() { let { matches } = React.useContext(RouteContext); let routeMatch = matches[matches.length - 1]; return routeMatch ? routeMatch.params : {}; } /** * Resolves the pathname of the given `to` value against the current location. * * @see https://reactrouter.com/hooks/use-resolved-path */ function useResolvedPath(to, { relative } = {}) { let { matches } = React.useContext(RouteContext); let { pathname: locationPathname } = useLocation(); let routePathnamesJson = JSON.stringify(UNSAFE_getPathContributingMatches(matches).map(match => match.pathnameBase)); return React.useMemo(() => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, relative === "path"), [to, routePathnamesJson, locationPathname, relative]); } /** * Returns the element of the route that matched the current location, prepared * with the correct context to render the remainder of the route tree. Route * elements in the tree must render an to render their child route's * element. * * @see https://reactrouter.com/hooks/use-routes */ function useRoutes(routes, locationArg) { !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useRoutes() may be used only in the context of a component.`) : void 0; let { navigator } = React.useContext(NavigationContext); let dataRouterStateContext = React.useContext(DataRouterStateContext); let { matches: parentMatches } = React.useContext(RouteContext); let routeMatch = parentMatches[parentMatches.length - 1]; let parentParams = routeMatch ? routeMatch.params : {}; let parentPathname = routeMatch ? routeMatch.pathname : "/"; let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/"; let parentRoute = routeMatch && routeMatch.route; { // You won't get a warning about 2 different under a // without a trailing *, but this is a best-effort warning anyway since we // cannot even give the warning unless they land at the parent route. // // Example: // // // {/* This route path MUST end with /* because otherwise // it will never match /blog/post/123 */} // } /> // } /> // // // function Blog() { // return ( // // } /> // // ); // } let parentPath = parentRoute && parentRoute.path || ""; warningOnce(parentPathname, !parentRoute || parentPath.endsWith("*"), `You rendered descendant (or called \`useRoutes()\`) at ` + `"${parentPathname}" (under ) 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 to .`); } let locationFromContext = useLocation(); let location; if (locationArg) { let parsedLocationArg = typeof locationArg === "string" ? parsePath(locationArg) : locationArg; !(parentPathnameBase === "/" || parsedLocationArg.pathname?.startsWith(parentPathnameBase)) ? invariant(false, `When overriding the location using \`\` 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; location = parsedLocationArg; } else { location = locationFromContext; } let pathname = location.pathname || "/"; let remainingPathname = parentPathnameBase === "/" ? pathname : pathname.slice(parentPathnameBase.length) || "/"; let matches = matchRoutes(routes, { pathname: remainingPathname }); { warning(parentRoute || matches != null, `No routes matched location "${location.pathname}${location.search}${location.hash}" `) ; 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 with a null value by default resulting in an "empty" page.`) ; } let renderedMatches = _renderMatches(matches && matches.map(match => Object.assign({}, match, { params: Object.assign({}, parentParams, match.params), pathname: joinPaths([parentPathnameBase, // Re-encode pathnames that were decoded inside matchRoutes navigator.encodeLocation ? navigator.encodeLocation(match.pathname).pathname : match.pathname]), pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase, // Re-encode pathnames that were decoded inside matchRoutes navigator.encodeLocation ? navigator.encodeLocation(match.pathnameBase).pathname : match.pathnameBase]) })), parentMatches, dataRouterStateContext || undefined); // When a user passes in a `locationArg`, the associated routes need to // be wrapped in a new `LocationContext.Provider` in order for `useLocation` // to use the scoped location instead of the global location. if (locationArg && renderedMatches) { return /*#__PURE__*/React.createElement(LocationContext.Provider, { value: { location: { pathname: "/", search: "", hash: "", state: null, key: "default", ...location }, navigationType: Action.Pop } }, renderedMatches); } return renderedMatches; } function DefaultErrorElement() { let error = useRouteError(); let message = isRouteErrorResponse(error) ? `${error.status} ${error.statusText}` : error instanceof Error ? error.message : JSON.stringify(error); let stack = error instanceof Error ? error.stack : null; let lightgrey = "rgba(200,200,200, 0.5)"; let preStyles = { padding: "0.5rem", backgroundColor: lightgrey }; let codeStyles = { padding: "2px 4px", backgroundColor: lightgrey }; let devInfo = null; { 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\u00A0", /*#__PURE__*/React.createElement("code", { style: codeStyles }, "errorElement"), " props on\u00A0", /*#__PURE__*/React.createElement("code", { style: codeStyles }, ""))); } return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h2", null, "Unexpected Application Error!"), /*#__PURE__*/React.createElement("h3", { style: { fontStyle: "italic" } }, message), stack ? /*#__PURE__*/React.createElement("pre", { style: preStyles }, stack) : null, devInfo); } class RenderErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { location: props.location, error: props.error }; } static getDerivedStateFromError(error) { return { error: error }; } static getDerivedStateFromProps(props, state) { // When we get into an error state, the user will likely click "back" to the // previous page that didn't have an error. Because this wraps the entire // application, that will have no effect--the error page continues to display. // This gives us a mechanism to recover from the error when the location changes. // // Whether we're in an error state or not, we update the location in state // so that when we are in an error state, it gets reset when a new location // comes in and the user recovers from the error. if (state.location !== props.location) { return { error: props.error, location: props.location }; } // If we're not changing locations, preserve the location but still surface // any new errors that may come through. We retain the existing error, we do // this because the error provided from the app state may be cleared without // the location changing. return { error: props.error || state.error, location: state.location }; } componentDidCatch(error, errorInfo) { console.error("React Router caught the following error during render", error, errorInfo); } render() { return this.state.error ? /*#__PURE__*/React.createElement(RouteContext.Provider, { value: this.props.routeContext }, /*#__PURE__*/React.createElement(RouteErrorContext.Provider, { value: this.state.error, children: this.props.component })) : this.props.children; } } function RenderedRoute({ routeContext, match, children }) { let dataRouterContext = React.useContext(DataRouterContext); // Track how deep we got in our render pass to emulate SSR componentDidCatch // in a DataStaticRouter if (dataRouterContext && dataRouterContext.static && dataRouterContext.staticContext && match.route.errorElement) { dataRouterContext.staticContext._deepestRenderedBoundaryId = match.route.id; } return /*#__PURE__*/React.createElement(RouteContext.Provider, { value: routeContext }, children); } function _renderMatches(matches, parentMatches = [], dataRouterState) { if (matches == null) { if (dataRouterState?.errors) { // Don't bail if we have data router errors so we can render them in the // boundary. Use the pre-matched (or shimmed) matches matches = dataRouterState.matches; } else { return null; } } let renderedMatches = matches; // If we have data errors, trim matches to the highest error boundary let errors = dataRouterState?.errors; if (errors != null) { let errorIndex = renderedMatches.findIndex(m => m.route.id && errors?.[m.route.id]); !(errorIndex >= 0) ? invariant(false, `Could not find a matching route for the current errors: ${errors}`) : void 0; renderedMatches = renderedMatches.slice(0, Math.min(renderedMatches.length, errorIndex + 1)); } return renderedMatches.reduceRight((outlet, match, index) => { let error = match.route.id ? errors?.[match.route.id] : null; // Only data routers handle errors let errorElement = dataRouterState ? match.route.errorElement || /*#__PURE__*/React.createElement(DefaultErrorElement, null) : null; let matches = parentMatches.concat(renderedMatches.slice(0, index + 1)); let getChildren = () => /*#__PURE__*/React.createElement(RenderedRoute, { match: match, routeContext: { outlet, matches } }, error ? errorElement : match.route.element !== undefined ? match.route.element : outlet); // Only wrap in an error boundary within data router usages when we have an // errorElement on this route. Otherwise let it bubble up to an ancestor // errorElement return dataRouterState && (match.route.errorElement || index === 0) ? /*#__PURE__*/React.createElement(RenderErrorBoundary, { location: dataRouterState.location, component: errorElement, error: error, children: getChildren(), routeContext: { outlet: null, matches } }) : getChildren(); }, null); } var DataRouterHook; (function (DataRouterHook) { DataRouterHook["UseBlocker"] = "useBlocker"; DataRouterHook["UseRevalidator"] = "useRevalidator"; })(DataRouterHook || (DataRouterHook = {})); var DataRouterStateHook; (function (DataRouterStateHook) { DataRouterStateHook["UseLoaderData"] = "useLoaderData"; DataRouterStateHook["UseActionData"] = "useActionData"; DataRouterStateHook["UseRouteError"] = "useRouteError"; DataRouterStateHook["UseNavigation"] = "useNavigation"; DataRouterStateHook["UseRouteLoaderData"] = "useRouteLoaderData"; DataRouterStateHook["UseMatches"] = "useMatches"; DataRouterStateHook["UseRevalidator"] = "useRevalidator"; })(DataRouterStateHook || (DataRouterStateHook = {})); function getDataRouterConsoleError(hookName) { return `${hookName} must be used within a data router. See https://reactrouter.com/routers/picking-a-router.`; } function useDataRouterContext(hookName) { let ctx = React.useContext(DataRouterContext); !ctx ? invariant(false, getDataRouterConsoleError(hookName)) : void 0; return ctx; } function useDataRouterState(hookName) { let state = React.useContext(DataRouterStateContext); !state ? invariant(false, getDataRouterConsoleError(hookName)) : void 0; return state; } function useRouteContext(hookName) { let route = React.useContext(RouteContext); !route ? invariant(false, getDataRouterConsoleError(hookName)) : void 0; return route; } function useCurrentRouteId(hookName) { let route = useRouteContext(hookName); let thisRoute = route.matches[route.matches.length - 1]; !thisRoute.route.id ? invariant(false, `${hookName} can only be used on routes that contain a unique "id"`) : void 0; return thisRoute.route.id; } /** * Returns the current navigation, defaulting to an "idle" navigation when * no navigation is in progress */ function useNavigation() { let state = useDataRouterState(DataRouterStateHook.UseNavigation); return state.navigation; } /** * Returns a revalidate function for manually triggering revalidation, as well * as the current state of any manual revalidations */ function useRevalidator() { let dataRouterContext = useDataRouterContext(DataRouterHook.UseRevalidator); let state = useDataRouterState(DataRouterStateHook.UseRevalidator); return { revalidate: dataRouterContext.router.revalidate, state: state.revalidation }; } /** * Returns the active route matches, useful for accessing loaderData for * parent/child routes or the route "handle" property */ function useMatches() { let { matches, loaderData } = useDataRouterState(DataRouterStateHook.UseMatches); return React.useMemo(() => matches.map(match => { let { pathname, params } = match; // Note: This structure matches that created by createUseMatchesMatch // in the @remix-run/router , so if you change this please also change // that :) Eventually we'll DRY this up return { id: match.route.id, pathname, params, data: loaderData[match.route.id], handle: match.route.handle }; }), [matches, loaderData]); } /** * Returns the loader data for the nearest ancestor Route loader */ function useLoaderData() { let state = useDataRouterState(DataRouterStateHook.UseLoaderData); let routeId = useCurrentRouteId(DataRouterStateHook.UseLoaderData); if (state.errors && state.errors[routeId] != null) { console.error(`You cannot \`useLoaderData\` in an errorElement (routeId: ${routeId})`); return undefined; } return state.loaderData[routeId]; } /** * Returns the loaderData for the given routeId */ function useRouteLoaderData(routeId) { let state = useDataRouterState(DataRouterStateHook.UseRouteLoaderData); return state.loaderData[routeId]; } /** * Returns the action data for the nearest ancestor Route action */ function useActionData() { let state = useDataRouterState(DataRouterStateHook.UseActionData); let route = React.useContext(RouteContext); !route ? invariant(false, `useActionData must be used inside a RouteContext`) : void 0; return Object.values(state?.actionData || {})[0]; } /** * Returns the nearest ancestor Route error, which could be a loader/action * error or a render error. This is intended to be called from your * errorElement to display a proper error message. */ function useRouteError() { let error = React.useContext(RouteErrorContext); let state = useDataRouterState(DataRouterStateHook.UseRouteError); let routeId = useCurrentRouteId(DataRouterStateHook.UseRouteError); // If this was a render error, we put it in a RouteError context inside // of RenderErrorBoundary if (error) { return error; } // Otherwise look for errors from our data router state return state.errors?.[routeId]; } /** * Returns the happy-path data from the nearest ancestor value */ function useAsyncValue() { let value = React.useContext(AwaitContext); return value?._data; } /** * Returns the error from the nearest ancestor value */ function useAsyncError() { let value = React.useContext(AwaitContext); return value?._error; } let blockerId = 0; /** * Allow the application to block navigations within the SPA and present the * user a confirmation dialog to confirm the navigation. Mostly used to avoid * using half-filled form data. This does not handle hard-reloads or * cross-origin navigations. */ function useBlocker(shouldBlock) { let { router } = useDataRouterContext(DataRouterHook.UseBlocker); let [blockerKey] = React.useState(() => String(++blockerId)); let blockerFunction = React.useCallback(args => { return typeof shouldBlock === "function" ? !!shouldBlock(args) : !!shouldBlock; }, [shouldBlock]); let blocker = router.getBlocker(blockerKey, blockerFunction); // Cleanup on unmount React.useEffect(() => () => router.deleteBlocker(blockerKey), [router, blockerKey]); return blocker; } const alreadyWarned = {}; function warningOnce(key, cond, message) { if (!cond && !alreadyWarned[key]) { alreadyWarned[key] = true; warning(false, message) ; } } /** * Given a Remix Router instance, render the appropriate UI */ function RouterProvider({ fallbackElement, router }) { // Sync router state to our component state to force re-renders let state = useSyncExternalStore(router.subscribe, () => router.state, // We have to provide this so React@18 doesn't complain during hydration, // but we pass our serialized hydration data into the router so state here // is already synced with what the server saw () => router.state); let navigator = React.useMemo(() => { return { createHref: router.createHref, encodeLocation: router.encodeLocation, go: n => router.navigate(n), push: (to, state, opts) => router.navigate(to, { state, preventScrollReset: opts?.preventScrollReset }), replace: (to, state, opts) => router.navigate(to, { replace: true, state, preventScrollReset: opts?.preventScrollReset }) }; }, [router]); let basename = router.basename || "/"; // The fragment and {null} here are important! We need them to keep React 18's // useId happy when we are server-rendering since we may have a