react-router
Version:
Declarative routing for React
842 lines (841 loc) • 30.4 kB
JavaScript
/**
* react-router v8.0.0
*
* 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 { createMemoryHistory, invariant, parsePath, warning } from "./router/history.js";
import { defaultMapRouteProperties, getResolveToMatches, getRoutePattern, resolveTo, stripBasename } from "./router/utils.js";
import { createRouter } from "./router/router.js";
import { AwaitContext, DataRouterContext, DataRouterStateContext, FetchersContext, LocationContext, NavigationContext, RouteContext, ViewTransitionContext, useIsRSCRouterContext } from "./context.js";
import { _renderMatches, useActionData, useAsyncValue, useInRouterContext, useLoaderData, useLocation, useMatches, useNavigate, useOutlet, useParams, useRouteError, useRoutes, useRoutesImpl } from "./hooks.js";
import { warnOnce } from "./server-runtime/warnings.js";
import * as React$1 from "react";
import { useOptimistic } from "react";
//#region lib/components.tsx
const hydrationRouteProperties = ["HydrateFallback", "hydrateFallbackElement"];
/**
* Create a new {@link DataRouter} that manages the application path using an
* in-memory [`History`](https://developer.mozilla.org/en-US/docs/Web/API/History)
* stack. Useful for non-browser environments without a DOM API.
*
* Data Routers should not be held in React state. You should create your router
* once outside of the React tree and pass it to {@link RouterProvider | `<RouterProvider>`}.
* You can use `patchRoutesOnNavigation` to add additional routes programmatically.
*
* @public
* @category Data Routers
* @mode data
* @param routes Application routes
* @param opts Options
* @param {MemoryRouterOpts.basename} opts.basename n/a
* @param {MemoryRouterOpts.dataStrategy} opts.dataStrategy n/a
* @param {MemoryRouterOpts.future} opts.future n/a
* @param {MemoryRouterOpts.getContext} opts.getContext n/a
* @param {MemoryRouterOpts.hydrationData} opts.hydrationData n/a
* @param {MemoryRouterOpts.initialEntries} opts.initialEntries n/a
* @param {MemoryRouterOpts.initialIndex} opts.initialIndex n/a
* @param {MemoryRouterOpts.instrumentations} opts.instrumentations n/a
* @param {MemoryRouterOpts.patchRoutesOnNavigation} opts.patchRoutesOnNavigation n/a
* @returns An initialized {@link DataRouter} to pass to {@link RouterProvider | `<RouterProvider>`}
*/
function createMemoryRouter(routes, opts) {
return createRouter({
basename: opts?.basename,
getContext: opts?.getContext,
future: opts?.future,
history: createMemoryHistory({
initialEntries: opts?.initialEntries,
initialIndex: opts?.initialIndex
}),
hydrationData: opts?.hydrationData,
routes,
mapRouteProperties: defaultMapRouteProperties,
hydrationRouteProperties,
dataStrategy: opts?.dataStrategy,
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
instrumentations: opts?.instrumentations
}).initialize();
}
var Deferred = class {
status = "pending";
promise;
resolve;
reject;
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = (value) => {
if (this.status === "pending") {
this.status = "resolved";
resolve(value);
}
};
this.reject = (reason) => {
if (this.status === "pending") {
this.status = "rejected";
reject(reason);
}
};
});
}
};
/**
* Render the UI for the given {@link DataRouter}. This component should
* typically be at the top of an app's element tree. The router prop should
* be a single router instance created outside of the React tree. Avoid
* creating new routers during React renders/re-renders.
*
* ```tsx
* import { createBrowserRouter } from "react-router";
* import { RouterProvider } from "react-router/dom";
* import { createRoot } from "react-dom/client";
*
* const router = createBrowserRouter(routes);
* createRoot(document.getElementById("root")).render(
* <RouterProvider router={router} />
* );
* ```
*
* <docs-info>Please note that this component is exported both from
* `react-router` and `react-router/dom` with the only difference being that the
* latter automatically wires up `react-dom`'s [`flushSync`](https://react.dev/reference/react-dom/flushSync)
* implementation. You _almost always_ want to use the version from
* `react-router/dom` unless you're running in a non-DOM environment.</docs-info>
*
*
* @public
* @category Data Routers
* @mode data
* @param props Props
* @param {RouterProviderProps.flushSync} props.flushSync n/a
* @param {RouterProviderProps.onError} props.onError n/a
* @param {RouterProviderProps.router} props.router n/a
* @param {RouterProviderProps.useTransitions} props.useTransitions n/a
* @returns React element for the rendered router
*/
function RouterProvider({ router, flushSync: reactDomFlushSyncImpl, onError, useTransitions }) {
useTransitions = useIsRSCRouterContext() || useTransitions;
let [_state, setStateImpl] = React$1.useState(router.state);
let [state, setOptimisticState] = useOptimistic(_state);
let [pendingState, setPendingState] = React$1.useState();
let [vtContext, setVtContext] = React$1.useState({ isTransitioning: false });
let [renderDfd, setRenderDfd] = React$1.useState();
let [transition, setTransition] = React$1.useState();
let [interruption, setInterruption] = React$1.useState();
let fetcherData = React$1.useRef(/* @__PURE__ */ new Map());
let setState = React$1.useCallback((newState, { deletedFetchers, newErrors, flushSync, viewTransitionOpts }) => {
if (newErrors && onError) Object.values(newErrors).forEach((error) => onError(error, {
location: newState.location,
params: newState.matches[0]?.params ?? {},
pattern: getRoutePattern(newState.matches)
}));
newState.fetchers.forEach((fetcher, key) => {
if (fetcher.data !== void 0) fetcherData.current.set(key, fetcher.data);
});
deletedFetchers.forEach((key) => fetcherData.current.delete(key));
warnOnce(flushSync === false || reactDomFlushSyncImpl != null, "You provided the `flushSync` option to a router update, but you are not using the `<RouterProvider>` from `react-router/dom` so `ReactDOM.flushSync()` is unavailable. Please update your app to `import { RouterProvider } from \"react-router/dom\"` and ensure you have `react-dom` installed as a dependency to use the `flushSync` option.");
let isViewTransitionAvailable = router.window != null && router.window.document != null && typeof router.window.document.startViewTransition === "function";
warnOnce(viewTransitionOpts == null || isViewTransitionAvailable, "You provided the `viewTransition` option to a router update, but you do not appear to be running in a DOM environment as `window.startViewTransition` is not available.");
if (!viewTransitionOpts || !isViewTransitionAvailable) {
if (reactDomFlushSyncImpl && flushSync) reactDomFlushSyncImpl(() => setStateImpl(newState));
else if (useTransitions === false) setStateImpl(newState);
else React$1.startTransition(() => {
if (useTransitions === true) setOptimisticState((s) => getOptimisticRouterState(s, newState));
setStateImpl(newState);
});
return;
}
if (reactDomFlushSyncImpl && flushSync) {
reactDomFlushSyncImpl(() => {
if (transition) {
renderDfd?.resolve();
transition.skipTransition();
}
setVtContext({
isTransitioning: true,
flushSync: true,
currentLocation: viewTransitionOpts.currentLocation,
nextLocation: viewTransitionOpts.nextLocation
});
});
let t = router.window.document.startViewTransition(() => {
reactDomFlushSyncImpl(() => setStateImpl(newState));
});
t.finished.finally(() => {
reactDomFlushSyncImpl(() => {
setRenderDfd(void 0);
setTransition(void 0);
setPendingState(void 0);
setVtContext({ isTransitioning: false });
});
});
reactDomFlushSyncImpl(() => setTransition(t));
return;
}
if (transition) {
renderDfd?.resolve();
transition.skipTransition();
setInterruption({
state: newState,
currentLocation: viewTransitionOpts.currentLocation,
nextLocation: viewTransitionOpts.nextLocation
});
} else {
setPendingState(newState);
setVtContext({
isTransitioning: true,
flushSync: false,
currentLocation: viewTransitionOpts.currentLocation,
nextLocation: viewTransitionOpts.nextLocation
});
}
}, [
router.window,
reactDomFlushSyncImpl,
transition,
renderDfd,
useTransitions,
setOptimisticState,
onError
]);
React$1.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
React$1.useEffect(() => {
if (vtContext.isTransitioning && !vtContext.flushSync) setRenderDfd(new Deferred());
}, [vtContext]);
React$1.useEffect(() => {
if (renderDfd && pendingState && router.window) {
let newState = pendingState;
let renderPromise = renderDfd.promise;
let transition = router.window.document.startViewTransition(async () => {
if (useTransitions === false) setStateImpl(newState);
else React$1.startTransition(() => {
if (useTransitions === true) setOptimisticState((s) => getOptimisticRouterState(s, newState));
setStateImpl(newState);
});
await renderPromise;
});
transition.finished.finally(() => {
setRenderDfd(void 0);
setTransition(void 0);
setPendingState(void 0);
setVtContext({ isTransitioning: false });
});
setTransition(transition);
}
}, [
pendingState,
renderDfd,
router.window,
useTransitions,
setOptimisticState
]);
React$1.useEffect(() => {
if (renderDfd && pendingState && state.location.key === pendingState.location.key) renderDfd.resolve();
}, [
renderDfd,
transition,
state.location,
pendingState
]);
React$1.useEffect(() => {
if (!vtContext.isTransitioning && interruption) {
setPendingState(interruption.state);
setVtContext({
isTransitioning: true,
flushSync: false,
currentLocation: interruption.currentLocation,
nextLocation: interruption.nextLocation
});
setInterruption(void 0);
}
}, [vtContext.isTransitioning, interruption]);
let navigator = React$1.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 || "/";
let dataRouterContext = React$1.useMemo(() => ({
router,
navigator,
static: false,
basename,
onError
}), [
router,
navigator,
basename,
onError
]);
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement(DataRouterContext.Provider, { value: dataRouterContext }, /* @__PURE__ */ React$1.createElement(DataRouterStateContext.Provider, { value: state }, /* @__PURE__ */ React$1.createElement(FetchersContext.Provider, { value: fetcherData.current }, /* @__PURE__ */ React$1.createElement(ViewTransitionContext.Provider, { value: vtContext }, /* @__PURE__ */ React$1.createElement(Router, {
basename,
location: state.location,
navigationType: state.historyAction,
navigator,
useTransitions
}, /* @__PURE__ */ React$1.createElement(MemoizedDataRoutes, {
routes: router.routes,
manifest: router.manifest,
future: router.future,
state,
isStatic: false,
onError
})))))), null);
}
function getOptimisticRouterState(currentState, newState) {
return {
...currentState,
navigation: newState.navigation.state !== "idle" ? newState.navigation : currentState.navigation,
revalidation: newState.revalidation !== "idle" ? newState.revalidation : currentState.revalidation,
actionData: newState.navigation.state !== "submitting" ? newState.actionData : currentState.actionData,
fetchers: newState.fetchers
};
}
const MemoizedDataRoutes = React$1.memo(DataRoutes);
function DataRoutes({ routes, manifest, future, state, isStatic, onError }) {
return useRoutesImpl(routes, void 0, {
manifest,
state,
isStatic,
onError,
future
});
}
/**
* A declarative {@link Router | `<Router>`} that stores all entries in memory.
*
* @public
* @category Declarative Routers
* @mode declarative
* @param props Props
* @param {MemoryRouterProps.basename} props.basename n/a
* @param {MemoryRouterProps.children} props.children n/a
* @param {MemoryRouterProps.initialEntries} props.initialEntries n/a
* @param {MemoryRouterProps.initialIndex} props.initialIndex n/a
* @param {MemoryRouterProps.useTransitions} props.useTransitions n/a
* @returns A declarative in-memory {@link Router | `<Router>`} for client-side
* routing.
*/
function MemoryRouter({ basename, children, initialEntries, initialIndex, useTransitions }) {
let historyRef = React$1.useRef(null);
if (historyRef.current == null) historyRef.current = createMemoryHistory({
initialEntries,
initialIndex,
v5Compat: true
});
let history = historyRef.current;
let [state, setStateImpl] = React$1.useState({
action: history.action,
location: history.location
});
let setState = React$1.useCallback((newState) => {
if (useTransitions === false) setStateImpl(newState);
else React$1.startTransition(() => setStateImpl(newState));
}, [useTransitions]);
React$1.useLayoutEffect(() => history.listen(setState), [history, setState]);
return /* @__PURE__ */ React$1.createElement(Router, {
basename,
children,
location: state.location,
navigationType: state.action,
navigator: history,
useTransitions
});
}
/**
* A component-based version of {@link useNavigate} to use in a
* [`React.Component` class](https://react.dev/reference/react/Component) where
* hooks cannot be used.
*
* It's recommended to avoid using this component in favor of {@link useNavigate}.
*
* @example
* <Navigate to="/tasks" />
*
* @public
* @category Components
* @param props Props
* @param {NavigateProps.relative} props.relative n/a
* @param {NavigateProps.replace} props.replace n/a
* @param {NavigateProps.state} props.state n/a
* @param {NavigateProps.to} props.to n/a
* @returns {void}
*
*/
function Navigate({ to, replace, state, relative }) {
invariant(useInRouterContext(), `<Navigate> may be used only in the context of a <Router> component.`);
let { static: isStatic } = React$1.useContext(NavigationContext);
warning(!isStatic, "<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.");
let { matches } = React$1.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
let navigate = useNavigate();
let path = resolveTo(to, getResolveToMatches(matches), locationPathname, relative === "path");
let jsonPath = JSON.stringify(path);
React$1.useEffect(() => {
navigate(JSON.parse(jsonPath), {
replace,
state,
relative
});
}, [
navigate,
jsonPath,
relative,
replace,
state
]);
return null;
}
/**
* Renders the matching child route of a parent route or nothing if no child
* route matches.
*
* @example
* import { Outlet } from "react-router";
*
* export default function SomeParent() {
* return (
* <div>
* <h1>Parent Content</h1>
* <Outlet />
* </div>
* );
* }
*
* @public
* @category Components
* @param props Props
* @param {OutletProps.context} props.context n/a
* @returns React element for the rendered outlet or `null` if no child route matches.
*/
function Outlet(props) {
return useOutlet(props.context);
}
/**
* Configures an element to render when a pattern matches the current location.
* It must be rendered within a {@link Routes} element. Note that these routes
* do not participate in data loading, actions, code splitting, or any other
* route module features.
*
* @example
* // Usually used in a declarative router
* function App() {
* return (
* <BrowserRouter>
* <Routes>
* <Route index element={<StepOne />} />
* <Route path="step-2" element={<StepTwo />} />
* <Route path="step-3" element={<StepThree />} />
* </Routes>
* </BrowserRouter>
* );
* }
*
* // But can be used with a data router as well if you prefer the JSX notation
* const routes = createRoutesFromElements(
* <>
* <Route index loader={step1Loader} Component={StepOne} />
* <Route path="step-2" loader={step2Loader} Component={StepTwo} />
* <Route path="step-3" loader={step3Loader} Component={StepThree} />
* </>
* );
*
* const router = createBrowserRouter(routes);
*
* function App() {
* return <RouterProvider router={router} />;
* }
*
* @public
* @category Components
* @param props Props
* @param {PathRouteProps.action} props.action n/a
* @param {PathRouteProps.caseSensitive} props.caseSensitive n/a
* @param {PathRouteProps.Component} props.Component n/a
* @param {PathRouteProps.children} props.children n/a
* @param {PathRouteProps.element} props.element n/a
* @param {PathRouteProps.ErrorBoundary} props.ErrorBoundary n/a
* @param {PathRouteProps.errorElement} props.errorElement n/a
* @param {PathRouteProps.handle} props.handle n/a
* @param {PathRouteProps.HydrateFallback} props.HydrateFallback n/a
* @param {PathRouteProps.hydrateFallbackElement} props.hydrateFallbackElement n/a
* @param {PathRouteProps.id} props.id n/a
* @param {PathRouteProps.index} props.index n/a
* @param {PathRouteProps.lazy} props.lazy n/a
* @param {PathRouteProps.loader} props.loader n/a
* @param {PathRouteProps.path} props.path n/a
* @param {PathRouteProps.shouldRevalidate} props.shouldRevalidate n/a
* @returns {void}
*/
function Route(props) {
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>.");
}
/**
* Provides location context for the rest of the app.
*
* Note: You usually won't render a `<Router>` directly. Instead, you'll render a
* router that is more specific to your environment such as a {@link BrowserRouter}
* in web browsers or a {@link ServerRouter} for server rendering.
*
* @public
* @category Declarative Routers
* @mode declarative
* @param props Props
* @param {RouterProps.basename} props.basename n/a
* @param {RouterProps.children} props.children n/a
* @param {RouterProps.location} props.location n/a
* @param {RouterProps.navigationType} props.navigationType n/a
* @param {RouterProps.navigator} props.navigator n/a
* @param {RouterProps.static} props.static n/a
* @param {RouterProps.useTransitions} props.useTransitions n/a
* @returns React element for the rendered router or `null` if the location does
* not match the {@link props.basename}
*/
function Router({ basename: basenameProp = "/", children = null, location: locationProp, navigationType = "POP", navigator, static: staticProp = false, useTransitions }) {
invariant(!useInRouterContext(), "You cannot render a <Router> inside another <Router>. You should never have more than one in your app.");
let basename = basenameProp.replace(/^\/*/, "/");
let navigationContext = React$1.useMemo(() => ({
basename,
navigator,
static: staticProp,
useTransitions,
future: {}
}), [
basename,
navigator,
staticProp,
useTransitions
]);
if (typeof locationProp === "string") locationProp = parsePath(locationProp);
let { pathname = "/", search = "", hash = "", state = null, key = "default", mask } = locationProp;
let locationContext = React$1.useMemo(() => {
let trailingPathname = stripBasename(pathname, basename);
if (trailingPathname == null) return null;
return {
location: {
pathname: trailingPathname,
search,
hash,
state,
key,
mask
},
navigationType
};
}, [
basename,
pathname,
search,
hash,
state,
key,
navigationType,
mask
]);
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.`);
if (locationContext == null) return null;
return /* @__PURE__ */ React$1.createElement(NavigationContext.Provider, { value: navigationContext }, /* @__PURE__ */ React$1.createElement(LocationContext.Provider, {
children,
value: locationContext
}));
}
/**
* Renders a branch of {@link Route | `<Route>`s} that best matches the current
* location. Note that these routes do not participate in [data loading](../../start/framework/route-module#loader),
* [`action`](../../start/framework/route-module#action), code splitting, or
* any other [route module](../../start/framework/route-module) features.
*
* @example
* import { Route, Routes } from "react-router";
*
* <Routes>
* <Route index element={<StepOne />} />
* <Route path="step-2" element={<StepTwo />} />
* <Route path="step-3" element={<StepThree />} />
* </Routes>
*
* @public
* @category Components
* @param props Props
* @param {RoutesProps.children} props.children n/a
* @param {RoutesProps.location} props.location n/a
* @returns React element for the rendered routes or `null` if no route matches
*/
function Routes({ children, location }) {
return useRoutes(createRoutesFromChildren(children), location);
}
/**
* Used to render promise values with automatic error handling.
*
* **Note:** `<Await>` expects to be rendered inside a [`<React.Suspense>`](https://react.dev/reference/react/Suspense)
*
* @example
* import { Await, useLoaderData } from "react-router";
*
* export async function loader() {
* // not awaited
* const reviews = getReviews();
* // awaited (blocks the transition)
* const book = await fetch("/api/book").then((res) => res.json());
* return { book, reviews };
* }
*
* function Book() {
* const { book, reviews } = useLoaderData();
* return (
* <div>
* <h1>{book.title}</h1>
* <p>{book.description}</p>
* <React.Suspense fallback={<ReviewsSkeleton />}>
* <Await
* resolve={reviews}
* errorElement={
* <div>Could not load reviews 😬</div>
* }
* children={(resolvedReviews) => (
* <Reviews items={resolvedReviews} />
* )}
* />
* </React.Suspense>
* </div>
* );
* }
*
* @public
* @category Components
* @mode framework
* @mode data
* @param props Props
* @param {AwaitProps.children} props.children n/a
* @param {AwaitProps.errorElement} props.errorElement n/a
* @param {AwaitProps.resolve} props.resolve n/a
* @returns React element for the rendered awaited value
*/
function Await({ children, errorElement, resolve }) {
let dataRouterContext = React$1.useContext(DataRouterContext);
let dataRouterStateContext = React$1.useContext(DataRouterStateContext);
let onError = React$1.useCallback((error, errorInfo) => {
if (dataRouterContext && dataRouterContext.onError && dataRouterStateContext) dataRouterContext.onError(error, {
location: dataRouterStateContext.location,
params: dataRouterStateContext.matches[0]?.params || {},
pattern: getRoutePattern(dataRouterStateContext.matches),
errorInfo
});
}, [dataRouterContext, dataRouterStateContext]);
return /* @__PURE__ */ React$1.createElement(AwaitErrorBoundary, {
resolve,
errorElement,
onError
}, /* @__PURE__ */ React$1.createElement(ResolveAwait, null, children));
}
var AwaitErrorBoundary = class extends React$1.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
static getDerivedStateFromError(error) {
return { error };
}
componentDidCatch(error, errorInfo) {
if (this.props.onError) this.props.onError(error, errorInfo);
else console.error("<Await> caught the following error during render", error, errorInfo);
}
render() {
let { children, errorElement, resolve } = this.props;
let promise = null;
let status = 0;
if (!(resolve instanceof Promise)) {
status = 1;
promise = Promise.resolve();
Object.defineProperty(promise, "_tracked", { get: () => true });
Object.defineProperty(promise, "_data", { get: () => resolve });
} else if (this.state.error) {
status = 2;
let renderError = this.state.error;
promise = Promise.reject().catch(() => {});
Object.defineProperty(promise, "_tracked", { get: () => true });
Object.defineProperty(promise, "_error", { get: () => renderError });
} else if (resolve._tracked) {
promise = resolve;
status = "_error" in promise ? 2 : "_data" in promise ? 1 : 0;
} else {
status = 0;
Object.defineProperty(resolve, "_tracked", { get: () => true });
promise = resolve.then((data) => Object.defineProperty(resolve, "_data", { get: () => data }), (error) => {
this.props.onError?.(error);
Object.defineProperty(resolve, "_error", { get: () => error });
});
}
if (status === 2 && !errorElement) throw promise._error;
if (status === 2) return /* @__PURE__ */ React$1.createElement(AwaitContext.Provider, {
value: promise,
children: errorElement
});
if (status === 1) return /* @__PURE__ */ React$1.createElement(AwaitContext.Provider, {
value: promise,
children
});
throw promise;
}
};
function ResolveAwait({ children }) {
let data = useAsyncValue();
let toRender = typeof children === "function" ? children(data) : children;
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, toRender);
}
/**
* Creates a route config from a React "children" object, which is usually
* either a `<Route>` element or an array of them. Used internally by
* `<Routes>` to create a route config from its children.
*
* @category Utils
* @mode data
* @param children The React children to convert into a route config
* @param parentPath The path of the parent route, used to generate unique IDs.
* @returns An array of {@link RouteObject}s that can be used with a {@link DataRouter}
*/
function createRoutesFromChildren(children, parentPath = []) {
let routes = [];
React$1.Children.forEach(children, (element, index) => {
if (!React$1.isValidElement(element)) return;
let treePath = [...parentPath, index];
if (element.type === React$1.Fragment) {
routes.push.apply(routes, createRoutesFromChildren(element.props.children, treePath));
return;
}
invariant(element.type === Route, `[${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>`);
let props = element.props;
invariant(!props.index || !props.children, "An index route cannot have child routes.");
let route = {
id: props.id || treePath.join("-"),
caseSensitive: props.caseSensitive,
element: props.element,
Component: props.Component,
index: props.index,
path: props.path,
middleware: props.middleware,
loader: props.loader,
action: props.action,
hydrateFallbackElement: props.hydrateFallbackElement,
HydrateFallback: props.HydrateFallback,
errorElement: props.errorElement,
ErrorBoundary: props.ErrorBoundary,
shouldRevalidate: props.shouldRevalidate,
handle: props.handle,
lazy: props.lazy
};
if (props.children) route.children = createRoutesFromChildren(props.children, treePath);
routes.push(route);
});
return routes;
}
/**
* Create route objects from JSX elements instead of arrays of objects.
*
* @example
* const routes = createRoutesFromElements(
* <>
* <Route index loader={step1Loader} Component={StepOne} />
* <Route path="step-2" loader={step2Loader} Component={StepTwo} />
* <Route path="step-3" loader={step3Loader} Component={StepThree} />
* </>
* );
*
* const router = createBrowserRouter(routes);
*
* function App() {
* return <RouterProvider router={router} />;
* }
*
* @name createRoutesFromElements
* @public
* @category Utils
* @mode data
* @param children The React children to convert into a route config
* @param parentPath The path of the parent route, used to generate unique IDs.
* This is used for internal recursion and is not intended to be used by the
* application developer.
* @returns An array of {@link RouteObject}s that can be used with a {@link DataRouter}
*/
const createRoutesFromElements = createRoutesFromChildren;
/**
* Renders the result of {@link matchRoutes} into a React element.
*
* @public
* @category Utils
* @param matches The array of {@link RouteMatch | route matches} to render
* @returns A React element that renders the matched routes or `null` if no matches
*/
function renderMatches(matches) {
return _renderMatches(matches);
}
function useRouteComponentProps() {
return {
params: useParams(),
loaderData: useLoaderData(),
actionData: useActionData(),
matches: useMatches()
};
}
function WithComponentProps({ children }) {
const props = useRouteComponentProps();
return React$1.cloneElement(children, props);
}
function withComponentProps(Component) {
return function WithComponentProps() {
const props = useRouteComponentProps();
return React$1.createElement(Component, props);
};
}
function useHydrateFallbackProps() {
return {
params: useParams(),
loaderData: useLoaderData(),
actionData: useActionData()
};
}
function WithHydrateFallbackProps({ children }) {
const props = useHydrateFallbackProps();
return React$1.cloneElement(children, props);
}
function withHydrateFallbackProps(HydrateFallback) {
return function WithHydrateFallbackProps() {
const props = useHydrateFallbackProps();
return React$1.createElement(HydrateFallback, props);
};
}
function useErrorBoundaryProps() {
return {
params: useParams(),
loaderData: useLoaderData(),
actionData: useActionData(),
error: useRouteError()
};
}
function WithErrorBoundaryProps({ children }) {
const props = useErrorBoundaryProps();
return React$1.cloneElement(children, props);
}
function withErrorBoundaryProps(ErrorBoundary) {
return function WithErrorBoundaryProps() {
const props = useErrorBoundaryProps();
return React$1.createElement(ErrorBoundary, props);
};
}
//#endregion
export { Await, DataRoutes, MemoryRouter, Navigate, Outlet, Route, Router, RouterProvider, Routes, WithComponentProps, WithErrorBoundaryProps, WithHydrateFallbackProps, createMemoryRouter, createRoutesFromChildren, createRoutesFromElements, hydrationRouteProperties, renderMatches, withComponentProps, withErrorBoundaryProps, withHydrateFallbackProps };