Release <ViewTransition /> to Canary (#34712)

## Overview

This PR ships the View Transition APIs to `react@canary`:
- [`<ViewTransition
/>`](https://react.dev/reference/react/ViewTransition)
-
[`addTransitionType`](https://react.dev/reference/react/addTransitionType)

This means these APIs are ready for final feedback and prepare for
semver stable release.

## What this means

Shipping `<ViewTransition />` and `addTransitionType` to canary means
they have gone through extensive testing in production, we are confident
in the stability of the APIs, and we are preparing to release it in a
future semver stable version.

Libraries and frameworks following the [Canary
Workflow](https://react.dev/blog/2023/05/03/react-canaries) should begin
implementing and testing these features.

## Why we follow the Canary Workflow

To prepare for semver stable, libraries should test canary features like
`<ViewTransition />` with `react@canary` to confirm compatibility and
prepare for the next semver release in a myriad of environments and
configurations used throughout the React ecosystem. This provides
libraries with ample time to catch any issues we missed before slamming
them with problems in the wider semver release.

Since these features have already gone through extensive production
testing, and we are confident they are stable, frameworks following the
[Canary Workflow](https://react.dev/blog/2023/05/03/react-canaries) can
also begin adopting canary features like `<ViewTransition />`.

This adoption is similar to how different Browsers implement new
proposed browser features before they are added to the standard. If a
frameworks adopts a canary feature, they are committing to stability for
their users by ensuring any API changes before a semver stable release
are opaque and non-breaking to their users.

Apps not using a framework are also free to adopt canary features like
`<ViewTransition>` as long as they follow the [Canary
Workflow](https://react.dev/blog/2023/05/03/react-canaries), but we
generally recommend waiting for a semver stable release unless you have
the capacity to commit to following along with the canary changes and
debugging library compatibility issues.

Waiting for semver stable means you're able to benefit from libraries
testing and confirming support, and use semver as signal for which
version of a library you can use with support of the feature.

## Docs 

Check out the ["React Labs: View Transitions, Activity, and
more"](https://react.dev/blog/2025/04/23/react-labs-view-transitions-activity-and-more#view-transitions)
blog post, and [the new docs for `<ViewTransition
/>`](https://react.dev/reference/react/ViewTransition) and
[`addTransitionType`](https://react.dev/reference/react/addTransitionType)
for more info.
This commit is contained in:
Sebastian "Sebbie" Silbermann 2025-10-07 06:23:34 +02:00 committed by GitHub
parent b65e6fc58b
commit 6a8c7fb6f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 36 additions and 24 deletions

View File

@ -3,7 +3,7 @@ import React, {
useLayoutEffect,
useEffect,
useState,
unstable_addTransitionType as addTransitionType,
addTransitionType,
} from 'react';
import Chrome from './Chrome.js';

View File

@ -1,6 +1,6 @@
import React, {
unstable_addTransitionType as addTransitionType,
unstable_ViewTransition as ViewTransition,
addTransitionType,
ViewTransition,
Activity,
useLayoutEffect,
useEffect,

View File

@ -251,7 +251,8 @@ describe('Store component filters', () => {
});
it('should filter ViewTransition', async () => {
const ViewTransition = React.unstable_ViewTransition;
const ViewTransition =
React.ViewTransition || React.unstable_ViewTransition;
if (ViewTransition != null) {
await actAsync(async () =>

View File

@ -40,7 +40,7 @@ describe('ReactDOMFizzViewTransition', () => {
Stream = require('stream');
Suspense = React.Suspense;
ViewTransition = React.unstable_ViewTransition;
ViewTransition = React.ViewTransition;
// Test Environment
const jsdom = new JSDOM(

View File

@ -29,7 +29,7 @@ describe('ReactFragment', () => {
React = require('react');
Suspense = React.Suspense;
Activity = React.Activity;
ViewTransition = React.unstable_ViewTransition;
ViewTransition = React.ViewTransition;
ReactNoop = require('react-noop-renderer');
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;

View File

@ -941,7 +941,7 @@ describe('ReactLazy', () => {
// @gate enableViewTransition
it('throws with a useful error when wrapping ViewTransition with lazy()', async () => {
const BadLazy = lazy(() => fakeImport(React.unstable_ViewTransition));
const BadLazy = lazy(() => fakeImport(React.ViewTransition));
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
@ -954,7 +954,7 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
await resolveFakeImport(React.unstable_ViewTransition);
await resolveFakeImport(React.ViewTransition);
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<BadLazy />

View File

@ -23,7 +23,7 @@ describe('ViewTransitionReactServer', () => {
jest.resetModules();
jest.mock('react', () => require('react/react.react-server'));
ReactServer = require('react');
ViewTransition = ReactServer.unstable_ViewTransition;
ViewTransition = ReactServer.ViewTransition;
ReactNoopFlightServer = require('react-noop-renderer/flight-server');
jest.resetModules();

View File

@ -45,6 +45,8 @@ export {
startTransition,
unstable_LegacyHidden,
Activity,
ViewTransition,
addTransitionType,
unstable_Scope,
unstable_SuspenseList,
unstable_TracingMarker,

View File

@ -33,9 +33,9 @@ export {
unstable_postpone,
unstable_getCacheForType,
unstable_SuspenseList,
unstable_ViewTransition,
ViewTransition,
unstable_startGestureTransition,
unstable_addTransitionType,
addTransitionType,
unstable_useCacheRefresh,
useId,
useCallback,

View File

@ -34,9 +34,9 @@ export {
unstable_postpone,
unstable_getCacheForType,
unstable_SuspenseList,
unstable_ViewTransition,
ViewTransition,
unstable_startGestureTransition,
unstable_addTransitionType,
addTransitionType,
unstable_useCacheRefresh,
useId,
useCallback,

View File

@ -39,9 +39,11 @@ export {
unstable_LegacyHidden,
unstable_Scope,
unstable_SuspenseList,
unstable_ViewTransition,
ViewTransition,
ViewTransition as unstable_ViewTransition,
unstable_TracingMarker,
unstable_addTransitionType,
addTransitionType,
addTransitionType as unstable_addTransitionType,
unstable_useCacheRefresh,
use,
useActionState,

View File

@ -48,8 +48,8 @@ export {
unstable_Scope,
unstable_SuspenseList,
unstable_TracingMarker,
unstable_ViewTransition,
unstable_addTransitionType,
ViewTransition,
addTransitionType,
unstable_getCacheForType,
unstable_useCacheRefresh,
useId,

View File

@ -18,6 +18,8 @@ export {
PureComponent,
StrictMode,
Suspense,
ViewTransition,
addTransitionType,
cloneElement,
createContext,
createElement,

View File

@ -18,6 +18,8 @@ export {
PureComponent,
StrictMode,
Suspense,
ViewTransition,
addTransitionType,
cloneElement,
createContext,
createElement,

View File

@ -125,8 +125,8 @@ export {
// enableTransitionTracing
REACT_TRACING_MARKER_TYPE as unstable_TracingMarker,
// enableViewTransition
REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition,
addTransitionType as unstable_addTransitionType,
REACT_VIEW_TRANSITION_TYPE as ViewTransition,
addTransitionType as addTransitionType,
// enableGestureTransition
startGestureTransition as unstable_startGestureTransition,
// DEV-only

View File

@ -63,6 +63,7 @@ export {
REACT_PROFILER_TYPE as Profiler,
REACT_STRICT_MODE_TYPE as StrictMode,
REACT_SUSPENSE_TYPE as Suspense,
REACT_VIEW_TRANSITION_TYPE as ViewTransition,
cloneElement,
createElement,
createRef,
@ -83,6 +84,5 @@ export {
version,
// Experimental
REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList,
REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition,
captureOwnerStack, // DEV-only
};

View File

@ -62,6 +62,7 @@ export {
REACT_PROFILER_TYPE as Profiler,
REACT_STRICT_MODE_TYPE as StrictMode,
REACT_SUSPENSE_TYPE as Suspense,
REACT_VIEW_TRANSITION_TYPE as ViewTransition,
cloneElement,
createElement,
createRef,
@ -82,5 +83,4 @@ export {
version,
// Experimental
REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList,
REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition,
};

View File

@ -53,6 +53,8 @@ export {
REACT_PROFILER_TYPE as Profiler,
REACT_STRICT_MODE_TYPE as StrictMode,
REACT_SUSPENSE_TYPE as Suspense,
REACT_VIEW_TRANSITION_TYPE as ViewTransition,
REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition,
cloneElement,
createElement,
createRef,
@ -71,5 +73,4 @@ export {
captureOwnerStack, // DEV-only
// Experimental
REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList,
REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition,
};

View File

@ -16,6 +16,7 @@ import {
REACT_PROFILER_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
REACT_VIEW_TRANSITION_TYPE,
} from 'shared/ReactSymbols';
import {
cloneElement,
@ -46,6 +47,7 @@ export {
REACT_PROFILER_TYPE as Profiler,
REACT_STRICT_MODE_TYPE as StrictMode,
REACT_SUSPENSE_TYPE as Suspense,
REACT_VIEW_TRANSITION_TYPE as ViewTransition,
cloneElement,
createElement,
createRef,

View File

@ -84,7 +84,7 @@ export const enablePostpone = __EXPERIMENTAL__;
export const enableHalt: boolean = true;
export const enableViewTransition = __EXPERIMENTAL__;
export const enableViewTransition: boolean = true;
export const enableGestureTransition = __EXPERIMENTAL__;

View File

@ -62,7 +62,7 @@ export const enableHydrationLaneScheduling: boolean = true;
export const enableYieldingBeforePassive: boolean = false;
export const enableThrottledScheduling: boolean = false;
export const enableViewTransition: boolean = false;
export const enableViewTransition: boolean = true;
export const enableGestureTransition: boolean = false;
export const enableScrollEndPolyfill: boolean = true;
export const enableSuspenseyImages: boolean = false;