New source found from dndbeyond.com

This commit is contained in:
2026-07-08 01:00:09 -07:00
parent 9a983a6d7b
commit dcefa0d2f2
2865 changed files with 222467 additions and 49053 deletions
+39 -26
View File
@@ -150,7 +150,10 @@ function isFormDataSubmitterSupported() {
return _formDataSupportsSubmitter;
}
export interface SubmitOptions {
/**
* Submit options shared by both navigations and fetchers
*/
interface SharedSubmitOptions {
/**
* The HTTP method used to submit the form. Overrides `<form method>`.
* Defaults to "GET".
@@ -169,28 +172,6 @@ export interface SubmitOptions {
*/
encType?: FormEncType;
/**
* Indicate a specific fetcherKey to use when using navigate=false
*/
fetcherKey?: string;
/**
* navigate=false will use a fetcher instead of a navigation
*/
navigate?: boolean;
/**
* Set `true` to replace the current entry in the browser's history stack
* instead of creating a new one (i.e. stay on "the same page"). Defaults
* to `false`.
*/
replace?: boolean;
/**
* State object to add to the history stack entry for this navigation
*/
state?: any;
/**
* Determines whether the form action is relative to the route hierarchy or
* the pathname. Use this if you want to opt out of navigating the route
@@ -205,14 +186,46 @@ export interface SubmitOptions {
preventScrollReset?: boolean;
/**
* Enable flushSync for this navigation's state updates
* Enable flushSync for this submission's state updates
*/
unstable_flushSync?: boolean;
flushSync?: boolean;
}
/**
* Submit options available to fetchers
*/
export interface FetcherSubmitOptions extends SharedSubmitOptions {}
/**
* Submit options available to navigations
*/
export interface SubmitOptions extends FetcherSubmitOptions {
/**
* Set `true` to replace the current entry in the browser's history stack
* instead of creating a new one (i.e. stay on "the same page"). Defaults
* to `false`.
*/
replace?: boolean;
/**
* State object to add to the history stack entry for this navigation
*/
state?: any;
/**
* Indicate a specific fetcherKey to use when using navigate=false
*/
fetcherKey?: string;
/**
* navigate=false will use a fetcher instead of a navigation
*/
navigate?: boolean;
/**
* Enable view transitions on this submission navigation
*/
unstable_viewTransition?: boolean;
viewTransition?: boolean;
}
const supportedFormEncTypes: Set<FormEncType> = new Set([
+89 -52
View File
@@ -13,8 +13,11 @@ import type {
Navigator,
RelativeRoutingType,
RouteObject,
RouterProps,
RouterProviderProps,
To,
DataStrategyFunction,
PatchRoutesOnNavigationFunction,
} from "react-router";
import {
Router,
@@ -30,15 +33,13 @@ import {
UNSAFE_DataRouterStateContext as DataRouterStateContext,
UNSAFE_NavigationContext as NavigationContext,
UNSAFE_RouteContext as RouteContext,
UNSAFE_logV6DeprecationWarnings as logV6DeprecationWarnings,
UNSAFE_mapRouteProperties as mapRouteProperties,
UNSAFE_useRouteId as useRouteId,
UNSAFE_useRoutesImpl as useRoutesImpl,
} from "react-router";
import type {
BrowserHistory,
unstable_DataStrategyFunction,
unstable_DataStrategyFunctionArgs,
unstable_DataStrategyMatch,
Fetcher,
FormEncType,
FormMethod,
@@ -72,6 +73,7 @@ import type {
ParamKeyValuePair,
URLSearchParamsInit,
SubmitTarget,
FetcherSubmitOptions,
} from "./dom";
import {
createSearchParams,
@@ -86,9 +88,6 @@ import {
////////////////////////////////////////////////////////////////////////////////
export type {
unstable_DataStrategyFunction,
unstable_DataStrategyFunctionArgs,
unstable_DataStrategyMatch,
FormEncType,
FormMethod,
GetScrollRestorationKeyFunction,
@@ -108,6 +107,10 @@ export type {
BlockerFunction,
DataRouteMatch,
DataRouteObject,
DataStrategyFunction,
DataStrategyFunctionArgs,
DataStrategyMatch,
DataStrategyResult,
ErrorResponse,
Fetcher,
FutureConfig,
@@ -130,6 +133,8 @@ export type {
OutletProps,
Params,
ParamParseKey,
PatchRoutesOnNavigationFunction,
PatchRoutesOnNavigationFunctionArgs,
Path,
PathMatch,
Pathname,
@@ -149,7 +154,6 @@ export type {
ShouldRevalidateFunctionArgs,
To,
UIMatch,
unstable_HandlerResult,
} from "react-router";
export {
AbortedDeferredError,
@@ -174,6 +178,7 @@ export {
parsePath,
redirect,
redirectDocument,
replace,
renderMatches,
resolvePath,
useActionData,
@@ -255,7 +260,8 @@ interface DOMRouterOpts {
basename?: string;
future?: Partial<Omit<RouterFutureConfig, "v7_prependBasename">>;
hydrationData?: HydrationState;
unstable_dataStrategy?: unstable_DataStrategyFunction;
dataStrategy?: DataStrategyFunction;
patchRoutesOnNavigation?: PatchRoutesOnNavigationFunction;
window?: Window;
}
@@ -273,7 +279,8 @@ export function createBrowserRouter(
hydrationData: opts?.hydrationData || parseHydrationData(),
routes,
mapRouteProperties,
unstable_dataStrategy: opts?.unstable_dataStrategy,
dataStrategy: opts?.dataStrategy,
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
window: opts?.window,
}).initialize();
}
@@ -292,7 +299,8 @@ export function createHashRouter(
hydrationData: opts?.hydrationData || parseHydrationData(),
routes,
mapRouteProperties,
unstable_dataStrategy: opts?.unstable_dataStrategy,
dataStrategy: opts?.dataStrategy,
patchRoutesOnNavigation: opts?.patchRoutesOnNavigation,
window: opts?.window,
}).initialize();
}
@@ -513,16 +521,16 @@ export function RouterProvider({
newState: RouterState,
{
deletedFetchers,
unstable_flushSync: flushSync,
unstable_viewTransitionOpts: viewTransitionOpts,
flushSync: flushSync,
viewTransitionOpts: viewTransitionOpts,
}
) => {
deletedFetchers.forEach((key) => fetcherData.current.delete(key));
newState.fetchers.forEach((fetcher, key) => {
if (fetcher.data !== undefined) {
fetcherData.current.set(key, fetcher.data);
}
});
deletedFetchers.forEach((key) => fetcherData.current.delete(key));
let isViewTransitionUnavailable =
router.window == null ||
@@ -702,6 +710,18 @@ export function RouterProvider({
[router, navigator, basename]
);
let routerFuture = React.useMemo<RouterProps["future"]>(
() => ({
v7_relativeSplatPath: router.future.v7_relativeSplatPath,
}),
[router.future.v7_relativeSplatPath]
);
React.useEffect(
() => logV6DeprecationWarnings(future, router.future),
[future, router.future]
);
// 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 <script> here
// containing the hydrated server-side staticContext (from StaticRouterProvider).
@@ -719,12 +739,10 @@ export function RouterProvider({
location={state.location}
navigationType={state.historyAction}
navigator={navigator}
future={{
v7_relativeSplatPath: router.future.v7_relativeSplatPath,
}}
future={routerFuture}
>
{state.initialized || router.future.v7_partialHydration ? (
<DataRoutes
<MemoizedDataRoutes
routes={router.routes}
future={router.future}
state={state}
@@ -742,6 +760,9 @@ export function RouterProvider({
);
}
// Memoize to avoid re-renders when updating `ViewTransitionContext`
const MemoizedDataRoutes = React.memo(DataRoutes);
function DataRoutes({
routes,
future,
@@ -792,6 +813,8 @@ export function BrowserRouter({
React.useLayoutEffect(() => history.listen(setState), [history, setState]);
React.useEffect(() => logV6DeprecationWarnings(future), [future]);
return (
<Router
basename={basename}
@@ -843,6 +866,8 @@ export function HashRouter({
React.useLayoutEffect(() => history.listen(setState), [history, setState]);
React.useEffect(() => logV6DeprecationWarnings(future), [future]);
return (
<Router
basename={basename}
@@ -890,6 +915,8 @@ function HistoryRouter({
React.useLayoutEffect(() => history.listen(setState), [history, setState]);
React.useEffect(() => logV6DeprecationWarnings(future), [future]);
return (
<Router
basename={basename}
@@ -916,7 +943,7 @@ export interface LinkProps
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
to: To;
unstable_viewTransition?: boolean;
viewTransition?: boolean;
}
const isBrowser =
@@ -940,7 +967,7 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
target,
to,
preventScrollReset,
unstable_viewTransition,
viewTransition,
...rest
},
ref
@@ -990,7 +1017,7 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
target,
preventScrollReset,
relative,
unstable_viewTransition,
viewTransition,
});
function handleClick(
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
@@ -1018,7 +1045,7 @@ if (__DEV__) {
Link.displayName = "Link";
}
type NavLinkRenderProps = {
export type NavLinkRenderProps = {
isActive: boolean;
isPending: boolean;
isTransitioning: boolean;
@@ -1047,7 +1074,7 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
end = false,
style: styleProp,
to,
unstable_viewTransition,
viewTransition,
children,
...rest
},
@@ -1062,7 +1089,7 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
// Conditional usage is OK here because the usage of a data router is static
// eslint-disable-next-line react-hooks/rules-of-hooks
useViewTransitionState(path) &&
unstable_viewTransition === true;
viewTransition === true;
let toPathname = navigator.encodeLocation
? navigator.encodeLocation(path).pathname
@@ -1146,7 +1173,7 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
ref={ref}
style={style}
to={to}
unstable_viewTransition={unstable_viewTransition}
viewTransition={viewTransition}
>
{typeof children === "function" ? children(renderProps) : children}
</Link>
@@ -1158,8 +1185,10 @@ if (__DEV__) {
NavLink.displayName = "NavLink";
}
export interface FetcherFormProps
extends React.FormHTMLAttributes<HTMLFormElement> {
/**
* Form props shared by navigations and fetchers
*/
interface SharedFormProps extends React.FormHTMLAttributes<HTMLFormElement> {
/**
* The HTTP verb to use when the form is submit. Supports "get", "post",
* "put", "delete", "patch".
@@ -1200,7 +1229,15 @@ export interface FetcherFormProps
onSubmit?: React.FormEventHandler<HTMLFormElement>;
}
export interface FormProps extends FetcherFormProps {
/**
* Form props available to fetchers
*/
export interface FetcherFormProps extends SharedFormProps {}
/**
* Form props available to navigations
*/
export interface FormProps extends SharedFormProps {
/**
* Indicate a specific fetcherKey to use when using navigate=false
*/
@@ -1231,7 +1268,7 @@ export interface FormProps extends FetcherFormProps {
/**
* Enable view transitions on this Form navigation
*/
unstable_viewTransition?: boolean;
viewTransition?: boolean;
}
type HTMLSubmitEvent = React.BaseSyntheticEvent<
@@ -1261,7 +1298,7 @@ export const Form = React.forwardRef<HTMLFormElement, FormProps>(
onSubmit,
relative,
preventScrollReset,
unstable_viewTransition,
viewTransition,
...props
},
forwardedRef
@@ -1291,7 +1328,7 @@ export const Form = React.forwardRef<HTMLFormElement, FormProps>(
state,
relative,
preventScrollReset,
unstable_viewTransition,
viewTransition,
});
};
@@ -1356,7 +1393,7 @@ enum DataRouterStateHook {
function getDataRouterConsoleError(
hookName: DataRouterHook | DataRouterStateHook
) {
return `${hookName} must be used within a data router. See https://reactrouter.com/routers/picking-a-router.`;
return `${hookName} must be used within a data router. See https://reactrouter.com/v6/routers/picking-a-router.`;
}
function useDataRouterContext(hookName: DataRouterHook) {
@@ -1386,14 +1423,14 @@ export function useLinkClickHandler<E extends Element = HTMLAnchorElement>(
state,
preventScrollReset,
relative,
unstable_viewTransition,
viewTransition,
}: {
target?: React.HTMLAttributeAnchorTarget;
replace?: boolean;
state?: any;
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
unstable_viewTransition?: boolean;
viewTransition?: boolean;
} = {}
): (event: React.MouseEvent<E, MouseEvent>) => void {
let navigate = useNavigate();
@@ -1417,7 +1454,7 @@ export function useLinkClickHandler<E extends Element = HTMLAnchorElement>(
state,
preventScrollReset,
relative,
unstable_viewTransition,
viewTransition,
});
}
},
@@ -1431,7 +1468,7 @@ export function useLinkClickHandler<E extends Element = HTMLAnchorElement>(
to,
preventScrollReset,
relative,
unstable_viewTransition,
viewTransition,
]
);
}
@@ -1448,11 +1485,7 @@ export function useSearchParams(
`You cannot use the \`useSearchParams\` hook in a browser that does not ` +
`support the URLSearchParams API. If you need to support Internet ` +
`Explorer 11, we recommend you load a polyfill such as ` +
`https://github.com/ungap/url-search-params\n\n` +
`If you're unsure how to load polyfills, we recommend you check out ` +
`https://polyfill.io/v3/ which provides some recommendations about how ` +
`to load polyfills only for users that need them, instead of for every ` +
`user.`
`https://github.com/ungap/url-search-params.`
);
let defaultSearchParamsRef = React.useRef(createSearchParams(defaultInit));
@@ -1523,7 +1556,7 @@ export interface FetcherSubmitFunction {
(
target: SubmitTarget,
// Fetchers cannot replace or set state because they are not navigation events
options?: Omit<SubmitOptions, "replace" | "state">
options?: FetcherSubmitOptions
): void;
}
@@ -1565,7 +1598,7 @@ export function useSubmit(): SubmitFunction {
body,
formMethod: options.method || (method as HTMLFormMethod),
formEncType: options.encType || (encType as FormEncType),
unstable_flushSync: options.unstable_flushSync,
flushSync: options.flushSync,
});
} else {
router.navigate(options.action || action, {
@@ -1577,8 +1610,8 @@ export function useSubmit(): SubmitFunction {
replace: options.replace,
state: options.state,
fromRouteId: currentRouteId,
unstable_flushSync: options.unstable_flushSync,
unstable_viewTransition: options.unstable_viewTransition,
flushSync: options.flushSync,
viewTransition: options.viewTransition,
});
}
},
@@ -1614,9 +1647,13 @@ export function useFormAction(
// since it might not apply to our contextual route. We add it back based
// on match.route.index below
let params = new URLSearchParams(path.search);
if (params.has("index") && params.get("index") === "") {
let indexValues = params.getAll("index");
let hasNakedIndexParam = indexValues.some((v) => v === "");
if (hasNakedIndexParam) {
params.delete("index");
path.search = params.toString() ? `?${params.toString()}` : "";
indexValues.filter((v) => v).forEach((v) => params.append("index", v));
let qs = params.toString();
path.search = qs ? `?${qs}` : "";
}
}
@@ -1643,7 +1680,7 @@ export type FetcherWithComponents<TData> = Fetcher<TData> & {
FetcherFormProps & React.RefAttributes<HTMLFormElement>
>;
submit: FetcherSubmitFunction;
load: (href: string, opts?: { unstable_flushSync?: boolean }) => void;
load: (href: string, opts?: { flushSync?: boolean }) => void;
};
// TODO: (v7) Change the useFetcher generic default from `any` to `unknown`
@@ -1693,7 +1730,7 @@ export function useFetcher<TData = any>({
// Fetcher additions
let load = React.useCallback(
(href: string, opts?: { unstable_flushSync?: boolean }) => {
(href: string, opts?: { flushSync?: boolean }) => {
invariant(routeId, "No routeId available for fetcher.load()");
router.fetch(fetcherKey, routeId, href, opts);
},
@@ -1986,7 +2023,7 @@ function useViewTransitionState(
invariant(
vtContext != null,
"`unstable_useViewTransitionState` must be used within `react-router-dom`'s `RouterProvider`. " +
"`useViewTransitionState` must be used within `react-router-dom`'s `RouterProvider`. " +
"Did you accidentally import `RouterProvider` from `react-router`?"
);
@@ -2009,11 +2046,11 @@ function useViewTransitionState(
// destination. This ensures that other PUSH navigations that reverse
// an indicated transition apply. I.e., on the list view you have:
//
// <NavLink to="/details/1" unstable_viewTransition>
// <NavLink to="/details/1" viewTransition>
//
// If you click the breadcrumb back to the list view:
//
// <NavLink to="/list" unstable_viewTransition>
// <NavLink to="/list" viewTransition>
//
// We should apply the transition because it's indicated as active going
// from /list -> /details/1 and therefore should be active on the reverse
@@ -2024,6 +2061,6 @@ function useViewTransitionState(
);
}
export { useViewTransitionState as unstable_useViewTransitionState };
export { useViewTransitionState as useViewTransitionState };
//#endregion
@@ -1,738 +0,0 @@
import type {
InitialEntry,
LazyRouteFunction,
Location,
MemoryHistory,
RelativeRoutingType,
Router as RemixRouter,
RouterState,
RouterSubscriber,
To,
TrackedPromise,
} from "@remix-run/router";
import {
AbortedDeferredError,
Action as NavigationType,
createMemoryHistory,
UNSAFE_getResolveToMatches as getResolveToMatches,
UNSAFE_invariant as invariant,
parsePath,
resolveTo,
stripBasename,
UNSAFE_warning as warning,
} from "@remix-run/router";
import * as React from "react";
import type {
DataRouteObject,
IndexRouteObject,
Navigator,
NonIndexRouteObject,
RouteMatch,
RouteObject,
} from "./context";
import {
AwaitContext,
DataRouterContext,
DataRouterStateContext,
LocationContext,
NavigationContext,
RouteContext,
} from "./context";
import {
_renderMatches,
useAsyncValue,
useInRouterContext,
useLocation,
useNavigate,
useOutlet,
useRoutes,
useRoutesImpl,
} from "./hooks";
export interface FutureConfig {
v7_relativeSplatPath: boolean;
v7_startTransition: boolean;
}
export interface RouterProviderProps {
fallbackElement?: React.ReactNode;
router: RemixRouter;
// Only accept future flags relevant to rendering behavior
// routing flags should be accessed via router.future
future?: Partial<Pick<FutureConfig, "v7_startTransition">>;
}
/**
Webpack + React 17 fails to compile on any of the following because webpack
complains that `startTransition` doesn't exist in `React`:
* import { startTransition } from "react"
* import * as React from from "react";
"startTransition" in React ? React.startTransition(() => setState()) : setState()
* import * as React from from "react";
"startTransition" in React ? React["startTransition"](() => setState()) : setState()
Moving it to a constant such as the following solves the Webpack/React 17 issue:
* import * as React from from "react";
const START_TRANSITION = "startTransition";
START_TRANSITION in React ? React[START_TRANSITION](() => setState()) : setState()
However, that introduces webpack/terser minification issues in production builds
in React 18 where minification/obfuscation ends up removing the call of
React.startTransition entirely from the first half of the ternary. Grabbing
this exported reference once up front resolves that issue.
See https://github.com/remix-run/react-router/issues/10579
*/
const START_TRANSITION = "startTransition";
const startTransitionImpl = React[START_TRANSITION];
/**
* Given a Remix Router instance, render the appropriate UI
*/
export function RouterProvider({
fallbackElement,
router,
future,
}: RouterProviderProps): React.ReactElement {
let [state, setStateImpl] = React.useState(router.state);
let { v7_startTransition } = future || {};
let setState = React.useCallback<RouterSubscriber>(
(newState: RouterState) => {
if (v7_startTransition && startTransitionImpl) {
startTransitionImpl(() => setStateImpl(newState));
} else {
setStateImpl(newState);
}
},
[setStateImpl, v7_startTransition]
);
// Need to use a layout effect here so we are subscribed early enough to
// pick up on any render-driven redirects/navigations (useEffect/<Navigate>)
React.useLayoutEffect(() => router.subscribe(setState), [router, setState]);
React.useEffect(() => {
warning(
fallbackElement == null || !router.future.v7_partialHydration,
"`<RouterProvider fallbackElement>` is deprecated when using " +
"`v7_partialHydration`, use a `HydrateFallback` component instead"
);
// Only log this once on initial mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
let navigator = React.useMemo((): Navigator => {
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.useMemo(
() => ({
router,
navigator,
static: false,
basename,
}),
[router, navigator, 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 <script> here
// containing the hydrated server-side staticContext (from StaticRouterProvider).
// useId relies on the component tree structure to generate deterministic id's
// so we need to ensure it remains the same on the client even though
// we don't need the <script> tag
return (
<>
<DataRouterContext.Provider value={dataRouterContext}>
<DataRouterStateContext.Provider value={state}>
<Router
basename={basename}
location={state.location}
navigationType={state.historyAction}
navigator={navigator}
future={{
v7_relativeSplatPath: router.future.v7_relativeSplatPath,
}}
>
{state.initialized || router.future.v7_partialHydration ? (
<DataRoutes
routes={router.routes}
future={router.future}
state={state}
/>
) : (
fallbackElement
)}
</Router>
</DataRouterStateContext.Provider>
</DataRouterContext.Provider>
{null}
</>
);
}
function DataRoutes({
routes,
future,
state,
}: {
routes: DataRouteObject[];
future: RemixRouter["future"];
state: RouterState;
}): React.ReactElement | null {
return useRoutesImpl(routes, undefined, state, future);
}
export interface MemoryRouterProps {
basename?: string;
children?: React.ReactNode;
initialEntries?: InitialEntry[];
initialIndex?: number;
future?: Partial<FutureConfig>;
}
/**
* A `<Router>` that stores all entries in memory.
*
* @see https://reactrouter.com/router-components/memory-router
*/
export function MemoryRouter({
basename,
children,
initialEntries,
initialIndex,
future,
}: MemoryRouterProps): React.ReactElement {
let historyRef = React.useRef<MemoryHistory>();
if (historyRef.current == null) {
historyRef.current = createMemoryHistory({
initialEntries,
initialIndex,
v5Compat: true,
});
}
let history = historyRef.current;
let [state, setStateImpl] = React.useState({
action: history.action,
location: history.location,
});
let { v7_startTransition } = future || {};
let setState = React.useCallback(
(newState: { action: NavigationType; location: Location }) => {
v7_startTransition && startTransitionImpl
? startTransitionImpl(() => setStateImpl(newState))
: setStateImpl(newState);
},
[setStateImpl, v7_startTransition]
);
React.useLayoutEffect(() => history.listen(setState), [history, setState]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
future={future}
/>
);
}
export interface NavigateProps {
to: To;
replace?: boolean;
state?: any;
relative?: RelativeRoutingType;
}
/**
* Changes the current location.
*
* Note: This API is mostly useful in React.Component subclasses that are not
* able to use hooks. In functional components, we recommend you use the
* `useNavigate` hook instead.
*
* @see https://reactrouter.com/components/navigate
*/
export function Navigate({
to,
replace,
state,
relative,
}: NavigateProps): null {
invariant(
useInRouterContext(),
// TODO: This error is probably because they somehow have 2 versions of
// the router loaded. We can help them understand how to avoid that.
`<Navigate> may be used only in the context of a <Router> component.`
);
let { future, static: isStatic } = React.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.useContext(RouteContext);
let { pathname: locationPathname } = useLocation();
let navigate = useNavigate();
// Resolve the path outside of the effect so that when effects run twice in
// StrictMode they navigate to the same place
let path = resolveTo(
to,
getResolveToMatches(matches, future.v7_relativeSplatPath),
locationPathname,
relative === "path"
);
let jsonPath = JSON.stringify(path);
React.useEffect(
() => navigate(JSON.parse(jsonPath), { replace, state, relative }),
[navigate, jsonPath, relative, replace, state]
);
return null;
}
export interface OutletProps {
context?: unknown;
}
/**
* Renders the child route's element, if there is one.
*
* @see https://reactrouter.com/components/outlet
*/
export function Outlet(props: OutletProps): React.ReactElement | null {
return useOutlet(props.context);
}
export interface PathRouteProps {
caseSensitive?: NonIndexRouteObject["caseSensitive"];
path?: NonIndexRouteObject["path"];
id?: NonIndexRouteObject["id"];
lazy?: LazyRouteFunction<NonIndexRouteObject>;
loader?: NonIndexRouteObject["loader"];
action?: NonIndexRouteObject["action"];
hasErrorBoundary?: NonIndexRouteObject["hasErrorBoundary"];
shouldRevalidate?: NonIndexRouteObject["shouldRevalidate"];
handle?: NonIndexRouteObject["handle"];
index?: false;
children?: React.ReactNode;
element?: React.ReactNode | null;
hydrateFallbackElement?: React.ReactNode | null;
errorElement?: React.ReactNode | null;
Component?: React.ComponentType | null;
HydrateFallback?: React.ComponentType | null;
ErrorBoundary?: React.ComponentType | null;
}
export interface LayoutRouteProps extends PathRouteProps {}
export interface IndexRouteProps {
caseSensitive?: IndexRouteObject["caseSensitive"];
path?: IndexRouteObject["path"];
id?: IndexRouteObject["id"];
lazy?: LazyRouteFunction<IndexRouteObject>;
loader?: IndexRouteObject["loader"];
action?: IndexRouteObject["action"];
hasErrorBoundary?: IndexRouteObject["hasErrorBoundary"];
shouldRevalidate?: IndexRouteObject["shouldRevalidate"];
handle?: IndexRouteObject["handle"];
index: true;
children?: undefined;
element?: React.ReactNode | null;
hydrateFallbackElement?: React.ReactNode | null;
errorElement?: React.ReactNode | null;
Component?: React.ComponentType | null;
HydrateFallback?: React.ComponentType | null;
ErrorBoundary?: React.ComponentType | null;
}
export type RouteProps = PathRouteProps | LayoutRouteProps | IndexRouteProps;
/**
* Declares an element that should be rendered at a certain URL path.
*
* @see https://reactrouter.com/components/route
*/
export function Route(_props: RouteProps): React.ReactElement | null {
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>.`
);
}
export interface RouterProps {
basename?: string;
children?: React.ReactNode;
location: Partial<Location> | string;
navigationType?: NavigationType;
navigator: Navigator;
static?: boolean;
future?: Partial<Pick<FutureConfig, "v7_relativeSplatPath">>;
}
/**
* 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 `<BrowserRouter>`
* in web browsers or a `<StaticRouter>` for server rendering.
*
* @see https://reactrouter.com/router-components/router
*/
export function Router({
basename: basenameProp = "/",
children = null,
location: locationProp,
navigationType = NavigationType.Pop,
navigator,
static: staticProp = false,
future,
}: RouterProps): React.ReactElement | null {
invariant(
!useInRouterContext(),
`You cannot render a <Router> inside another <Router>.` +
` You should never have more than one in your app.`
);
// Preserve trailing slashes on basename, so we can let the user control
// the enforcement of trailing slashes throughout the app
let basename = basenameProp.replace(/^\/*/, "/");
let navigationContext = React.useMemo(
() => ({
basename,
navigator,
static: staticProp,
future: {
v7_relativeSplatPath: false,
...future,
},
}),
[basename, future, navigator, staticProp]
);
if (typeof locationProp === "string") {
locationProp = parsePath(locationProp);
}
let {
pathname = "/",
search = "",
hash = "",
state = null,
key = "default",
} = locationProp;
let locationContext = React.useMemo(() => {
let trailingPathname = stripBasename(pathname, basename);
if (trailingPathname == null) {
return null;
}
return {
location: {
pathname: trailingPathname,
search,
hash,
state,
key,
},
navigationType,
};
}, [basename, pathname, search, hash, state, key, navigationType]);
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 (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider children={children} value={locationContext} />
</NavigationContext.Provider>
);
}
export interface RoutesProps {
children?: React.ReactNode;
location?: Partial<Location> | string;
}
/**
* A container for a nested tree of `<Route>` elements that renders the branch
* that best matches the current location.
*
* @see https://reactrouter.com/components/routes
*/
export function Routes({
children,
location,
}: RoutesProps): React.ReactElement | null {
return useRoutes(createRoutesFromChildren(children), location);
}
export interface AwaitResolveRenderFunction {
(data: Awaited<any>): React.ReactNode;
}
export interface AwaitProps {
children: React.ReactNode | AwaitResolveRenderFunction;
errorElement?: React.ReactNode;
resolve: TrackedPromise | any;
}
/**
* Component to use for rendering lazily loaded data from returning defer()
* in a loader function
*/
export function Await({ children, errorElement, resolve }: AwaitProps) {
return (
<AwaitErrorBoundary resolve={resolve} errorElement={errorElement}>
<ResolveAwait>{children}</ResolveAwait>
</AwaitErrorBoundary>
);
}
type AwaitErrorBoundaryProps = React.PropsWithChildren<{
errorElement?: React.ReactNode;
resolve: TrackedPromise | any;
}>;
type AwaitErrorBoundaryState = {
error: any;
};
enum AwaitRenderStatus {
pending,
success,
error,
}
const neverSettledPromise = new Promise(() => {});
class AwaitErrorBoundary extends React.Component<
AwaitErrorBoundaryProps,
AwaitErrorBoundaryState
> {
constructor(props: AwaitErrorBoundaryProps) {
super(props);
this.state = { error: null };
}
static getDerivedStateFromError(error: any) {
return { error };
}
componentDidCatch(error: any, errorInfo: any) {
console.error(
"<Await> caught the following error during render",
error,
errorInfo
);
}
render() {
let { children, errorElement, resolve } = this.props;
let promise: TrackedPromise | null = null;
let status: AwaitRenderStatus = AwaitRenderStatus.pending;
if (!(resolve instanceof Promise)) {
// Didn't get a promise - provide as a resolved promise
status = AwaitRenderStatus.success;
promise = Promise.resolve();
Object.defineProperty(promise, "_tracked", { get: () => true });
Object.defineProperty(promise, "_data", { get: () => resolve });
} else if (this.state.error) {
// Caught a render error, provide it as a rejected promise
status = AwaitRenderStatus.error;
let renderError = this.state.error;
promise = Promise.reject().catch(() => {}); // Avoid unhandled rejection warnings
Object.defineProperty(promise, "_tracked", { get: () => true });
Object.defineProperty(promise, "_error", { get: () => renderError });
} else if ((resolve as TrackedPromise)._tracked) {
// Already tracked promise - check contents
promise = resolve;
status =
"_error" in promise
? AwaitRenderStatus.error
: "_data" in promise
? AwaitRenderStatus.success
: AwaitRenderStatus.pending;
} else {
// Raw (untracked) promise - track it
status = AwaitRenderStatus.pending;
Object.defineProperty(resolve, "_tracked", { get: () => true });
promise = resolve.then(
(data: any) =>
Object.defineProperty(resolve, "_data", { get: () => data }),
(error: any) =>
Object.defineProperty(resolve, "_error", { get: () => error })
);
}
if (
status === AwaitRenderStatus.error &&
promise._error instanceof AbortedDeferredError
) {
// Freeze the UI by throwing a never resolved promise
throw neverSettledPromise;
}
if (status === AwaitRenderStatus.error && !errorElement) {
// No errorElement, throw to the nearest route-level error boundary
throw promise._error;
}
if (status === AwaitRenderStatus.error) {
// Render via our errorElement
return <AwaitContext.Provider value={promise} children={errorElement} />;
}
if (status === AwaitRenderStatus.success) {
// Render children with resolved value
return <AwaitContext.Provider value={promise} children={children} />;
}
// Throw to the suspense boundary
throw promise;
}
}
/**
* @private
* Indirection to leverage useAsyncValue for a render-prop API on `<Await>`
*/
function ResolveAwait({
children,
}: {
children: React.ReactNode | AwaitResolveRenderFunction;
}) {
let data = useAsyncValue();
let toRender = typeof children === "function" ? children(data) : children;
return <>{toRender}</>;
}
///////////////////////////////////////////////////////////////////////////////
// UTILS
///////////////////////////////////////////////////////////////////////////////
/**
* 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.
*
* @see https://reactrouter.com/utils/create-routes-from-children
*/
export function createRoutesFromChildren(
children: React.ReactNode,
parentPath: number[] = []
): RouteObject[] {
let routes: RouteObject[] = [];
React.Children.forEach(children, (element, index) => {
if (!React.isValidElement(element)) {
// Ignore non-elements. This allows people to more easily inline
// conditionals in their route config.
return;
}
let treePath = [...parentPath, index];
if (element.type === React.Fragment) {
// Transparently support React.Fragment and its children.
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>`
);
invariant(
!element.props.index || !element.props.children,
"An index route cannot have child routes."
);
let route: RouteObject = {
id: element.props.id || treePath.join("-"),
caseSensitive: element.props.caseSensitive,
element: element.props.element,
Component: element.props.Component,
index: element.props.index,
path: element.props.path,
loader: element.props.loader,
action: element.props.action,
errorElement: element.props.errorElement,
ErrorBoundary: element.props.ErrorBoundary,
hasErrorBoundary:
element.props.ErrorBoundary != null ||
element.props.errorElement != null,
shouldRevalidate: element.props.shouldRevalidate,
handle: element.props.handle,
lazy: element.props.lazy,
};
if (element.props.children) {
route.children = createRoutesFromChildren(
element.props.children,
treePath
);
}
routes.push(route);
});
return routes;
}
/**
* Renders the result of `matchRoutes()` into a React element.
*/
export function renderMatches(
matches: RouteMatch[] | null
): React.ReactElement | null {
return _renderMatches(matches);
}
@@ -1,176 +0,0 @@
import * as React from "react";
import type {
AgnosticIndexRouteObject,
AgnosticNonIndexRouteObject,
AgnosticRouteMatch,
History,
LazyRouteFunction,
Location,
Action as NavigationType,
RelativeRoutingType,
Router,
StaticHandlerContext,
To,
TrackedPromise,
} from "@remix-run/router";
// Create react-specific types from the agnostic types in @remix-run/router to
// export from react-router
export interface IndexRouteObject {
caseSensitive?: AgnosticIndexRouteObject["caseSensitive"];
path?: AgnosticIndexRouteObject["path"];
id?: AgnosticIndexRouteObject["id"];
loader?: AgnosticIndexRouteObject["loader"];
action?: AgnosticIndexRouteObject["action"];
hasErrorBoundary?: AgnosticIndexRouteObject["hasErrorBoundary"];
shouldRevalidate?: AgnosticIndexRouteObject["shouldRevalidate"];
handle?: AgnosticIndexRouteObject["handle"];
index: true;
children?: undefined;
element?: React.ReactNode | null;
hydrateFallbackElement?: React.ReactNode | null;
errorElement?: React.ReactNode | null;
Component?: React.ComponentType | null;
HydrateFallback?: React.ComponentType | null;
ErrorBoundary?: React.ComponentType | null;
lazy?: LazyRouteFunction<RouteObject>;
}
export interface NonIndexRouteObject {
caseSensitive?: AgnosticNonIndexRouteObject["caseSensitive"];
path?: AgnosticNonIndexRouteObject["path"];
id?: AgnosticNonIndexRouteObject["id"];
loader?: AgnosticNonIndexRouteObject["loader"];
action?: AgnosticNonIndexRouteObject["action"];
hasErrorBoundary?: AgnosticNonIndexRouteObject["hasErrorBoundary"];
shouldRevalidate?: AgnosticNonIndexRouteObject["shouldRevalidate"];
handle?: AgnosticNonIndexRouteObject["handle"];
index?: false;
children?: RouteObject[];
element?: React.ReactNode | null;
hydrateFallbackElement?: React.ReactNode | null;
errorElement?: React.ReactNode | null;
Component?: React.ComponentType | null;
HydrateFallback?: React.ComponentType | null;
ErrorBoundary?: React.ComponentType | null;
lazy?: LazyRouteFunction<RouteObject>;
}
export type RouteObject = IndexRouteObject | NonIndexRouteObject;
export type DataRouteObject = RouteObject & {
children?: DataRouteObject[];
id: string;
};
export interface RouteMatch<
ParamKey extends string = string,
RouteObjectType extends RouteObject = RouteObject
> extends AgnosticRouteMatch<ParamKey, RouteObjectType> {}
export interface DataRouteMatch extends RouteMatch<string, DataRouteObject> {}
export interface DataRouterContextObject
// Omit `future` since those can be pulled from the `router`
// `NavigationContext` needs future since it doesn't have a `router` in all cases
extends Omit<NavigationContextObject, "future"> {
router: Router;
staticContext?: StaticHandlerContext;
}
export const DataRouterContext =
React.createContext<DataRouterContextObject | null>(null);
if (__DEV__) {
DataRouterContext.displayName = "DataRouter";
}
export const DataRouterStateContext = React.createContext<
Router["state"] | null
>(null);
if (__DEV__) {
DataRouterStateContext.displayName = "DataRouterState";
}
export const AwaitContext = React.createContext<TrackedPromise | null>(null);
if (__DEV__) {
AwaitContext.displayName = "Await";
}
export interface NavigateOptions {
replace?: boolean;
state?: any;
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
unstable_flushSync?: boolean;
unstable_viewTransition?: boolean;
}
/**
* A Navigator is a "location changer"; it's how you get to different locations.
*
* Every history instance conforms to the Navigator interface, but the
* distinction is useful primarily when it comes to the low-level `<Router>` API
* where both the location and a navigator must be provided separately in order
* to avoid "tearing" that may occur in a suspense-enabled app if the action
* and/or location were to be read directly from the history instance.
*/
export interface Navigator {
createHref: History["createHref"];
// Optional for backwards-compat with Router/HistoryRouter usage (edge case)
encodeLocation?: History["encodeLocation"];
go: History["go"];
push(to: To, state?: any, opts?: NavigateOptions): void;
replace(to: To, state?: any, opts?: NavigateOptions): void;
}
interface NavigationContextObject {
basename: string;
navigator: Navigator;
static: boolean;
future: {
v7_relativeSplatPath: boolean;
};
}
export const NavigationContext = React.createContext<NavigationContextObject>(
null!
);
if (__DEV__) {
NavigationContext.displayName = "Navigation";
}
interface LocationContextObject {
location: Location;
navigationType: NavigationType;
}
export const LocationContext = React.createContext<LocationContextObject>(
null!
);
if (__DEV__) {
LocationContext.displayName = "Location";
}
export interface RouteContextObject {
outlet: React.ReactElement | null;
matches: RouteMatch[];
isDataRoute: boolean;
}
export const RouteContext = React.createContext<RouteContextObject>({
outlet: null,
matches: [],
isDataRoute: false,
});
if (__DEV__) {
RouteContext.displayName = "Route";
}
export const RouteErrorContext = React.createContext<any>(null);
if (__DEV__) {
RouteErrorContext.displayName = "RouteError";
}
File diff suppressed because it is too large Load Diff