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__;