diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
index 7a930f8fe6e0..77771b5b19a6 100644
--- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
@@ -107,6 +107,7 @@ import {
disableCommentsAsDOMContainers,
enableSuspenseyImages,
enableSrcObject,
+ enableViewTransition,
} from 'shared/ReactFeatureFlags';
import {
HostComponent,
@@ -5112,7 +5113,7 @@ export function isHostHoistableType(
}
export function maySuspendCommit(type: Type, props: Props): boolean {
- if (!enableSuspenseyImages) {
+ if (!enableSuspenseyImages && !enableViewTransition) {
return false;
}
// Suspensey images are the default, unless you opt-out of with either
@@ -5206,7 +5207,7 @@ export function suspendInstance(
type: Type,
props: Props,
): void {
- if (!enableSuspenseyImages) {
+ if (!enableSuspenseyImages && !enableViewTransition) {
return;
}
if (suspendedState === null) {
diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js
index 614df0831f66..c2ea817f5b1a 100644
--- a/packages/react-reconciler/src/ReactFiber.js
+++ b/packages/react-reconciler/src/ReactFiber.js
@@ -41,6 +41,7 @@ import {
disableLegacyMode,
enableObjectFiber,
enableViewTransition,
+ enableSuspenseyImages,
} from 'shared/ReactFeatureFlags';
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
import {ConcurrentRoot} from './ReactRootTags';
@@ -89,6 +90,7 @@ import {
StrictLegacyMode,
StrictEffectsMode,
NoStrictPassiveEffectsMode,
+ SuspenseyImagesMode,
} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
@@ -875,6 +877,11 @@ export function createFiberFromViewTransition(
lanes: Lanes,
key: null | string,
): Fiber {
+ if (!enableSuspenseyImages) {
+ // Render a ViewTransition component opts into SuspenseyImages mode even
+ // when the flag is off.
+ mode |= SuspenseyImagesMode;
+ }
const fiber = createFiber(ViewTransitionComponent, pendingProps, key, mode);
fiber.elementType = REACT_VIEW_TRANSITION_TYPE;
fiber.lanes = lanes;
diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js
index 037c1d3bef42..740de057d265 100644
--- a/packages/react-reconciler/src/ReactFiberCompleteWork.js
+++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js
@@ -42,6 +42,7 @@ import {
disableLegacyMode,
enableSiblingPrerendering,
enableViewTransition,
+ enableSuspenseyImages,
} from 'shared/ReactFeatureFlags';
import {now} from './Scheduler';
@@ -77,7 +78,12 @@ import {
ViewTransitionComponent,
ActivityComponent,
} from './ReactWorkTags';
-import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
+import {
+ NoMode,
+ ConcurrentMode,
+ ProfileMode,
+ SuspenseyImagesMode,
+} from './ReactTypeOfMode';
import {
Placement,
Update,
@@ -555,9 +561,11 @@ function preloadInstanceAndSuspendIfNeeded(
renderLanes: Lanes,
) {
const maySuspend =
- oldProps === null
+ (enableSuspenseyImages ||
+ (workInProgress.mode & SuspenseyImagesMode) !== NoMode) &&
+ (oldProps === null
? maySuspendCommit(type, newProps)
- : maySuspendCommitOnUpdate(type, oldProps, newProps);
+ : maySuspendCommitOnUpdate(type, oldProps, newProps));
if (!maySuspend) {
// If this flag was set previously, we can remove it. The flag
diff --git a/packages/react-reconciler/src/ReactTypeOfMode.js b/packages/react-reconciler/src/ReactTypeOfMode.js
index fd67d9979f8b..4348fe266864 100644
--- a/packages/react-reconciler/src/ReactTypeOfMode.js
+++ b/packages/react-reconciler/src/ReactTypeOfMode.js
@@ -12,8 +12,11 @@ export type TypeOfMode = number;
export const NoMode = /* */ 0b0000000;
// TODO: Remove ConcurrentMode by reading from the root tag instead
export const ConcurrentMode = /* */ 0b0000001;
-export const ProfileMode = /* */ 0b0000010;
+export const ProfileMode = /* */ 0b0000010;
//export const DebugTracingMode = /* */ 0b0000100; // Removed
export const StrictLegacyMode = /* */ 0b0001000;
export const StrictEffectsMode = /* */ 0b0010000;
export const NoStrictPassiveEffectsMode = /* */ 0b1000000;
+// Keep track of if we're in a SuspenseyImages eligible subtree.
+// TODO: Remove this when enableSuspenseyImages ship where it's always on.
+export const SuspenseyImagesMode = /* */ 0b0100000;
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseyCommitPhase-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseyCommitPhase-test.js
index 2b5707b8c554..cad1a011817f 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseyCommitPhase-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseyCommitPhase-test.js
@@ -50,6 +50,7 @@ describe('ReactSuspenseyCommitPhase', () => {
);
}
+ // @gate enableSuspenseyImages
it('suspend commit during initial mount', async () => {
const root = ReactNoop.createRoot();
await act(async () => {
@@ -70,6 +71,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('suspend commit during update', async () => {
const root = ReactNoop.createRoot();
await act(() => resolveSuspenseyThing('A'));
@@ -105,6 +107,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('suspend commit during initial mount at the root', async () => {
const root = ReactNoop.createRoot();
await act(async () => {
@@ -121,6 +124,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('suspend commit during update at the root', async () => {
const root = ReactNoop.createRoot();
await act(() => resolveSuspenseyThing('A'));
@@ -147,6 +151,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('suspend commit during urgent initial mount', async () => {
const root = ReactNoop.createRoot();
await act(async () => {
@@ -165,6 +170,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('suspend commit during urgent update', async () => {
const root = ReactNoop.createRoot();
await act(() => resolveSuspenseyThing('A'));
@@ -203,6 +209,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('suspends commit during urgent initial mount at the root', async () => {
const root = ReactNoop.createRoot();
await act(async () => {
@@ -217,6 +224,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('suspends commit during urgent update at the root', async () => {
const root = ReactNoop.createRoot();
await act(() => resolveSuspenseyThing('A'));
@@ -239,6 +247,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('does suspend commit during urgent initial mount at the root when sync rendering', async () => {
const root = ReactNoop.createRoot();
await act(async () => {
@@ -256,6 +265,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('does suspend commit during urgent update at the root when sync rendering', async () => {
const root = ReactNoop.createRoot();
await act(() => resolveSuspenseyThing('A'));
@@ -283,6 +293,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableSuspenseyImages
it('an urgent update interrupts a suspended commit', async () => {
const root = ReactNoop.createRoot();
@@ -305,6 +316,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput('Something else');
});
+ // @gate enableSuspenseyImages
it('a transition update interrupts a suspended commit', async () => {
const root = ReactNoop.createRoot();
@@ -329,7 +341,7 @@ describe('ReactSuspenseyCommitPhase', () => {
expect(root).toMatchRenderedOutput('Something else');
});
- // @gate enableSuspenseList
+ // @gate enableSuspenseList && enableSuspenseyImages
it('demonstrate current behavior when used with SuspenseList (not ideal)', async () => {
function App() {
return (
@@ -381,6 +393,7 @@ describe('ReactSuspenseyCommitPhase', () => {
);
});
+ // @gate enableSuspenseyImages
it('avoid triggering a fallback if resource loads immediately', async () => {
const root = ReactNoop.createRoot();
await act(async () => {
@@ -429,7 +442,7 @@ describe('ReactSuspenseyCommitPhase', () => {
);
});
- // @gate enableActivity
+ // @gate enableActivity && enableSuspenseyImages
it("host instances don't suspend during prerendering, but do suspend when they are revealed", async () => {
function More() {
Scheduler.log('More');
@@ -493,7 +506,7 @@ describe('ReactSuspenseyCommitPhase', () => {
});
// FIXME: Should pass with `enableYieldingBeforePassive`
- // @gate !enableYieldingBeforePassive
+ // @gate !enableYieldingBeforePassive && enableSuspenseyImages
it('runs passive effects after suspended commit resolves', async () => {
function Effect() {
React.useEffect(() => {
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index aed9b9bcfdb2..10bd08970f8e 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -96,7 +96,7 @@ export const enableGestureTransition = __EXPERIMENTAL__;
export const enableScrollEndPolyfill = __EXPERIMENTAL__;
-export const enableSuspenseyImages = __EXPERIMENTAL__;
+export const enableSuspenseyImages = false;
export const enableSrcObject = __EXPERIMENTAL__;