diff --git a/packages/docs/src/pages/ApiComponentsPage.tsx b/packages/docs/src/pages/ApiComponentsPage.tsx
index 67dc4ba..22a4a8b 100644
--- a/packages/docs/src/pages/ApiComponentsPage.tsx
+++ b/packages/docs/src/pages/ApiComponentsPage.tsx
@@ -102,6 +102,21 @@ export function ApiComponentsPage() {
takes over. See the SSR guide for details.
+
features
diff --git a/packages/docs/src/pages/LearnTransitionsPage.tsx b/packages/docs/src/pages/LearnTransitionsPage.tsx
index 03ed2e0..55bde8d 100644
--- a/packages/docs/src/pages/LearnTransitionsPage.tsx
+++ b/packages/docs/src/pages/LearnTransitionsPage.tsx
@@ -64,7 +64,7 @@ function UserDetail({ data }: { data: Promise }) {
- Tagging Navigations with experimentalTransitionTypes
+ Tagging Navigations with transitionTypes
React's{" "}
@@ -77,15 +77,14 @@ function UserDetail({ data }: { data: Promise }) {
"navigation" by default.
- You can replace the default by passing an experimentalTransitionTypes{" "}
- function to {""}. The function receives the destination{" "}
- url and the underlying navigationType (one of push,{" "}
- replace, reload, traverse) and returns an array of
- transition types:
+ You can replace the default by passing a transitionTypes function to{" "}
+ {""}. The function receives the destination url and the
+ underlying navigationType (one of push, replace,{" "}
+ reload, traverse) and returns an array of transition types:
{` [
+ transitionTypes={({ navigationType, url }) => [
"navigation",
\`navigation-\${navigationType}\`,
url.pathname.startsWith("/admin") ? "admin" : "public",
@@ -95,14 +94,14 @@ function UserDetail({ data }: { data: Promise }) {
The returned types replace the default — include{" "}
"navigation" yourself if you want it.
-
- React Canary required. At the time of writing,{" "}
- addTransitionType is only available in React Canary (exported as{" "}
- unstable_addTransitionType). On stable React, the prop is still invoked but
- the returned types are silently discarded. The prop is named with an{" "}
- experimental prefix to reflect this; it will be renamed once{" "}
- addTransitionType becomes stable in React.
-
+
+ addTransitionType is available in React 19.3 or later. On older React
+ versions, the function is still invoked but the returned types are silently discarded.
+
+
+ The prop was previously named experimentalTransitionTypes. The old name is
+ still accepted as a deprecated alias and will be removed in the next major version.
+
diff --git a/packages/router/src/Router/index.tsx b/packages/router/src/Router/index.tsx
index 1a63ea1..9d09c86 100644
--- a/packages/router/src/Router/index.tsx
+++ b/packages/router/src/Router/index.tsx
@@ -111,20 +111,22 @@ export type RouterProps = {
*/
trailingSlash?: TrailingSlashMode;
/**
- * **Experimental.** Function returning the React transition types to attach
- * to entry-change transitions via `addTransitionType`. Called inside
- * `startTransition` for each navigation; the returned types replace the
- * default `["navigation"]`.
+ * Function returning the React transition types to attach to entry-change
+ * transitions via `addTransitionType`. Called inside `startTransition` for
+ * each navigation; the returned types replace the default `["navigation"]`.
*
- * Requires a React build that exports `addTransitionType` (currently React
- * Canary). On builds that don't expose the API, the function is still
+ * Requires a React build that exports `addTransitionType` (React 19.3 or
+ * later). On builds that don't expose the API, the function is still
* invoked but the types are silently discarded.
*
- * The `experimental` prefix will be dropped once `addTransitionType`
- * becomes stable in React.
- *
* @default () => ["navigation"]
*/
+ transitionTypes?: GetTransitionTypes;
+ /**
+ * @deprecated Use {@link RouterProps.transitionTypes} instead. This alias
+ * will be removed in the next major version. When both are given,
+ * `transitionTypes` takes precedence.
+ */
experimentalTransitionTypes?: GetTransitionTypes;
/**
* Opt-in feature flags that change the Router's behavior.
@@ -170,6 +172,7 @@ export function Router({
fallback = "none",
ssr,
trailingSlash,
+ transitionTypes,
experimentalTransitionTypes,
features,
}: RouterProps): ReactNode {
@@ -230,10 +233,11 @@ export function Router({
// Resolve transition types for the current navigation. Wrapped in
// useEffectEvent so the subscription effect doesn't re-run when the
- // `experimentalTransitionTypes` prop identity changes.
- const getTransitionTypes = useEffectEvent((context: TransitionTypeContext): readonly string[] =>
- experimentalTransitionTypes ? experimentalTransitionTypes(context) : ["navigation"],
- );
+ // `transitionTypes` prop identity changes.
+ const getTransitionTypes = useEffectEvent((context: TransitionTypeContext): readonly string[] => {
+ const getTypes = transitionTypes ?? experimentalTransitionTypes;
+ return getTypes ? getTypes(context) : ["navigation"];
+ });
// Subscribe to navigation changes (conditionally wrapped in transition)
useEffect(() => {
diff --git a/packages/router/src/__tests__/Router.test.tsx b/packages/router/src/__tests__/Router.test.tsx
index a0d59f5..8915796 100644
--- a/packages/router/src/__tests__/Router.test.tsx
+++ b/packages/router/src/__tests__/Router.test.tsx
@@ -295,7 +295,7 @@ describe("Router", () => {
});
});
- describe("experimentalTransitionTypes", () => {
+ describe("transitionTypes", () => {
let addTransitionTypeSpy: ReturnType;
beforeEach(() => {
@@ -324,7 +324,7 @@ describe("Router", () => {
expect(addTransitionTypeSpy).toHaveBeenCalledWith("navigation");
});
- it("uses the returned types when experimentalTransitionTypes is provided", () => {
+ it("uses the returned types when transitionTypes is provided", () => {
const getTypes: GetTransitionTypes = vi.fn(() => ["page", "fade"]);
const routes: RouteDefinition[] = [
@@ -332,7 +332,7 @@ describe("Router", () => {
{ path: "/about", component: () => About },
];
- render();
+ render();
addTransitionTypeSpy.mockClear();
@@ -346,6 +346,54 @@ describe("Router", () => {
expect(addTransitionTypeSpy).not.toHaveBeenCalledWith("navigation");
});
+ it("supports the deprecated experimentalTransitionTypes alias", () => {
+ const getTypes: GetTransitionTypes = () => ["legacy"];
+
+ const routes: RouteDefinition[] = [
+ { path: "/", component: () => Home },
+ { path: "/about", component: () => About },
+ ];
+
+ render();
+
+ addTransitionTypeSpy.mockClear();
+
+ act(() => {
+ mockNavigation.__simulateNavigation("http://localhost/about");
+ });
+
+ expect(addTransitionTypeSpy).toHaveBeenCalledTimes(1);
+ expect(addTransitionTypeSpy).toHaveBeenCalledWith("legacy");
+ });
+
+ it("prefers transitionTypes over experimentalTransitionTypes", () => {
+ const getTypes: GetTransitionTypes = () => ["new"];
+ const getLegacyTypes: GetTransitionTypes = vi.fn(() => ["legacy"]);
+
+ const routes: RouteDefinition[] = [
+ { path: "/", component: () => Home },
+ { path: "/about", component: () => About },
+ ];
+
+ render(
+ ,
+ );
+
+ addTransitionTypeSpy.mockClear();
+
+ act(() => {
+ mockNavigation.__simulateNavigation("http://localhost/about");
+ });
+
+ expect(addTransitionTypeSpy).toHaveBeenCalledTimes(1);
+ expect(addTransitionTypeSpy).toHaveBeenCalledWith("new");
+ expect(getLegacyTypes).not.toHaveBeenCalled();
+ });
+
it("passes the new URL and navigationType to the callback", () => {
const calls: TransitionTypeContext[] = [];
const getTypes: GetTransitionTypes = (ctx) => {
@@ -358,7 +406,7 @@ describe("Router", () => {
{ path: "/about", component: () => About },
];
- render();
+ render();
// Push
act(() => {
diff --git a/packages/router/src/core/addTransitionType.ts b/packages/router/src/core/addTransitionType.ts
index d868584..aeac5aa 100644
--- a/packages/router/src/core/addTransitionType.ts
+++ b/packages/router/src/core/addTransitionType.ts
@@ -7,8 +7,9 @@ const candidate = reactExports.addTransitionType ?? reactExports.unstable_addTra
/**
* Wrapper around React's `addTransitionType` that degrades to a no-op when
- * the host React build doesn't expose the API (e.g. stable React 19.2).
- * `addTransitionType` is currently available only in React Canary.
+ * the host React build doesn't expose the API (e.g. React 19.2).
+ * `addTransitionType` is available in React starting with 19.3; older Canary
+ * builds export it as `unstable_addTransitionType`.
*/
export const addTransitionType: AddTransitionType =
typeof candidate === "function" ? (candidate as AddTransitionType) : () => {};
diff --git a/packages/router/src/types.ts b/packages/router/src/types.ts
index 52f0039..3098401 100644
--- a/packages/router/src/types.ts
+++ b/packages/router/src/types.ts
@@ -109,7 +109,7 @@ export type OnNavigateInfo = {
export type NavigationType = "push" | "replace" | "reload" | "traverse";
/**
- * Context passed to an `experimentalTransitionTypes` callback.
+ * Context passed to a `transitionTypes` callback.
*/
export type TransitionTypeContext = {
/** URL of the new navigation entry. */
@@ -122,7 +122,7 @@ export type TransitionTypeContext = {
* Returns the React transition types to associate with a navigation entry
* change. Called inside `startTransition` for each navigation; the return
* value is passed to React's `addTransitionType`. Requires a React build
- * that exports `addTransitionType` (currently React Canary).
+ * that exports `addTransitionType` (React 19.3 or later).
*/
export type GetTransitionTypes = (context: TransitionTypeContext) => readonly string[];
|