diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 087d74d628724..3a6ac01f98161 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -1018,6 +1018,7 @@ describe('ReactHooksInspectionIntegration', () => { ]); }); + // @gate enableUseMutableSource it('should support composite useMutableSource hook', () => { const createMutableSource = React.createMutableSource || React.unstable_createMutableSource; diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index c3b76549a22aa..732f7d71a7e71 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -31,6 +31,7 @@ import { enableStrictEffects, enableLazyContextPropagation, enableSuspenseLayoutEffectSemantics, + enableUseMutableSource, } from 'shared/ReactFeatureFlags'; import { @@ -1052,6 +1053,10 @@ function useMutableSource( getSnapshot: MutableSourceGetSnapshotFn, subscribe: MutableSourceSubscribeFn, ): Snapshot { + if (!enableUseMutableSource) { + return (undefined: any); + } + const root = ((getWorkInProgressRoot(): any): FiberRoot); if (root === null) { @@ -1213,6 +1218,10 @@ function mountMutableSource( getSnapshot: MutableSourceGetSnapshotFn, subscribe: MutableSourceSubscribeFn, ): Snapshot { + if (!enableUseMutableSource) { + return (undefined: any); + } + const hook = mountWorkInProgressHook(); hook.memoizedState = ({ refs: { @@ -1230,6 +1239,10 @@ function updateMutableSource( getSnapshot: MutableSourceGetSnapshotFn, subscribe: MutableSourceSubscribeFn, ): Snapshot { + if (!enableUseMutableSource) { + return (undefined: any); + } + const hook = updateWorkInProgressHook(); return useMutableSource(hook, source, getSnapshot, subscribe); } diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index 8bc1510deb455..b78f24e8b47f8 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -31,6 +31,7 @@ import { enableStrictEffects, enableLazyContextPropagation, enableSuspenseLayoutEffectSemantics, + enableUseMutableSource, } from 'shared/ReactFeatureFlags'; import { @@ -1052,6 +1053,10 @@ function useMutableSource( getSnapshot: MutableSourceGetSnapshotFn, subscribe: MutableSourceSubscribeFn, ): Snapshot { + if (!enableUseMutableSource) { + return (undefined: any); + } + const root = ((getWorkInProgressRoot(): any): FiberRoot); if (root === null) { @@ -1213,6 +1218,10 @@ function mountMutableSource( getSnapshot: MutableSourceGetSnapshotFn, subscribe: MutableSourceSubscribeFn, ): Snapshot { + if (!enableUseMutableSource) { + return (undefined: any); + } + const hook = mountWorkInProgressHook(); hook.memoizedState = ({ refs: { @@ -1230,6 +1239,10 @@ function updateMutableSource( getSnapshot: MutableSourceGetSnapshotFn, subscribe: MutableSourceSubscribeFn, ): Snapshot { + if (!enableUseMutableSource) { + return (undefined: any); + } + const hook = updateWorkInProgressHook(); return useMutableSource(hook, source, getSnapshot, subscribe); } diff --git a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js index a6d99f9802084..11cabd6f3175a 100644 --- a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js +++ b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js @@ -141,6 +141,7 @@ describe('useMutableSource', () => { beforeEach(loadModules); + // @gate enableUseMutableSource it('should subscribe to a source and schedule updates when it changes', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -208,6 +209,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should restart work if a new source is mutated during render', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -263,6 +265,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should schedule an update if a new source is mutated between render and commit (subscription)', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -302,6 +305,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should unsubscribe and resubscribe if a new source is used', () => { const sourceA = createSource('a-one'); const mutableSourceA = createMutableSource( @@ -358,6 +362,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should unsubscribe and resubscribe if a new subscribe function is provided', () => { const source = createSource('a-one'); const mutableSource = createMutableSource(source, param => param.version); @@ -422,6 +427,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should re-use previously read snapshot value when reading is unsafe', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -484,6 +490,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should read from source on newly mounted subtree if no pending updates are scheduled for source', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -523,6 +530,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should throw and restart render if source and snapshot are unavailable during an update', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -586,6 +594,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should throw and restart render if source and snapshot are unavailable during a sync update', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -649,6 +658,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should only update components whose subscriptions fire', () => { const source = createComplexSource('a:one', 'b:one'); const mutableSource = createMutableSource(source, param => param.version); @@ -687,6 +697,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should detect tearing in part of the store not yet subscribed to', () => { const source = createComplexSource('a:one', 'b:one'); const mutableSource = createMutableSource(source, param => param.version); @@ -779,6 +790,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('does not schedule an update for subscriptions that fire with an unchanged snapshot', () => { const MockComponent = jest.fn(Component); @@ -805,6 +817,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should throw and restart if getSnapshot changes between scheduled update and re-render', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -845,6 +858,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should recover from a mutation during yield when other work is scheduled', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -899,6 +913,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should not throw if the new getSnapshot returns the same snapshot value', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -953,6 +968,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should not throw if getSnapshot changes but the source can be safely read from anyway', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -992,6 +1008,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should still schedule an update if an eager selector throws after a mutation', () => { const source = createSource({ friends: [ @@ -1058,6 +1075,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should not warn about updates that fire between unmount and passive unsubscribe', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -1094,6 +1112,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should support inline selectors and updates that are processed after selector change', async () => { const source = createSource({ a: 'initial', @@ -1138,6 +1157,7 @@ describe('useMutableSource', () => { expect(root).toMatchRenderedOutput('Another update'); }); + // @gate enableUseMutableSource it('should clear the update queue when getSnapshot changes with pending lower priority updates', async () => { const source = createSource({ a: 'initial', @@ -1194,6 +1214,7 @@ describe('useMutableSource', () => { expect(root).toMatchRenderedOutput('B: Update'); }); + // @gate enableUseMutableSource it('should clear the update queue when source changes with pending lower priority updates', async () => { const sourceA = createSource('initial'); const sourceB = createSource('initial'); @@ -1238,6 +1259,7 @@ describe('useMutableSource', () => { expect(root).toMatchRenderedOutput('B: Update'); }); + // @gate enableUseMutableSource it('should always treat reading as potentially unsafe when getSnapshot changes between renders', async () => { const source = createSource({ a: 'foo', @@ -1327,6 +1349,7 @@ describe('useMutableSource', () => { expect(Scheduler).toHaveYielded(['x: bar, y: bar']); }); + // @gate enableUseMutableSource it('getSnapshot changes and then source is mutated in between paint and passive effect phase', async () => { const source = createSource({ a: 'foo', @@ -1385,6 +1408,7 @@ describe('useMutableSource', () => { expect(root).toMatchRenderedOutput('baz'); }); + // @gate enableUseMutableSource it('getSnapshot changes and then source is mutated in between paint and passive effect phase, case 2', async () => { const source = createSource({ a: 'a0', @@ -1455,6 +1479,7 @@ describe('useMutableSource', () => { expect(root.getChildrenAsJSX()).toEqual('first: a1, second: a1'); }); + // @gate enableUseMutableSource it( 'if source is mutated after initial read but before subscription is set ' + 'up, should still entangle all pending mutations even if snapshot of ' + @@ -1559,6 +1584,7 @@ describe('useMutableSource', () => { }, ); + // @gate enableUseMutableSource it('warns about functions being used as snapshot values', async () => { const source = createSource(() => 'a'); const mutableSource = createMutableSource(source, param => param.version); @@ -1586,6 +1612,7 @@ describe('useMutableSource', () => { expect(root).toMatchRenderedOutput('a'); }); + // @gate enableUseMutableSource it('getSnapshot changes and then source is mutated during interleaved event', async () => { const {useEffect} = React; @@ -1710,6 +1737,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should not tear with newly mounted component when updates were scheduled at a lower priority', async () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -1789,6 +1817,7 @@ describe('useMutableSource', () => { if (__DEV__) { describe('dev warnings', () => { + // @gate enableUseMutableSource it('should warn if the subscribe function does not return an unsubscribe function', () => { const source = createSource('one'); const mutableSource = createMutableSource( @@ -1814,6 +1843,7 @@ describe('useMutableSource', () => { ); }); + // @gate enableUseMutableSource it('should error if multiple renderers of the same type use a mutable source at the same time', () => { const source = createSource('one'); const mutableSource = createMutableSource( @@ -1894,6 +1924,7 @@ describe('useMutableSource', () => { }); }); + // @gate enableUseMutableSource it('should error if multiple renderers of the same type use a mutable source at the same time with mutation between', () => { const source = createSource('one'); const mutableSource = createMutableSource( diff --git a/packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js b/packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js index 7f46d1cb00552..61ebbe45e90c1 100644 --- a/packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js +++ b/packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js @@ -144,6 +144,7 @@ describe('useMutableSourceHydration', () => { return
{`${label}:${snapshot}`}
; } + // @gate enableUseMutableSource it('should render and hydrate', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -180,6 +181,7 @@ describe('useMutableSourceHydration', () => { expect(source.listenerCount).toBe(1); }); + // @gate enableUseMutableSource it('should detect a tear before hydrating a component', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -224,6 +226,7 @@ describe('useMutableSourceHydration', () => { expect(source.listenerCount).toBe(1); }); + // @gate enableUseMutableSource it('should detect a tear between hydrating components', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); @@ -282,6 +285,7 @@ describe('useMutableSourceHydration', () => { expect(source.listenerCount).toBe(2); }); + // @gate enableUseMutableSource it('should detect a tear between hydrating components reading from different parts of a source', () => { const source = createComplexSource('a:one', 'b:one'); const mutableSource = createMutableSource(source, param => param.version); @@ -371,6 +375,7 @@ describe('useMutableSourceHydration', () => { }); // @gate !enableSyncDefaultUpdates + // @gate enableUseMutableSource it('should detect a tear during a higher priority interruption', () => { const source = createSource('one'); const mutableSource = createMutableSource(source, param => param.version); diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js index 4b4fa89e01898..24fc9782595d4 100644 --- a/packages/react/index.experimental.js +++ b/packages/react/index.experimental.js @@ -46,7 +46,6 @@ export { useInsertionEffect, useLayoutEffect, useMemo, - useMutableSource as unstable_useMutableSource, useSyncExternalStore, useReducer, useRef, diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js index 3a0600d11a713..867980fa5389d 100644 --- a/packages/react/index.stable.js +++ b/packages/react/index.stable.js @@ -39,7 +39,6 @@ export { useInsertionEffect, useLayoutEffect, useMemo, - useMutableSource as unstable_useMutableSource, useSyncExternalStore, useReducer, useRef, diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index f5d34e2ff6539..112c2d10f2cbd 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -185,3 +185,6 @@ export const allowConcurrentByDefault = false; export const enablePersistentOffscreenHostContainer = false; export const consoleManagedByDevToolsDuringStrictMode = true; + +// Only enabled in www builds +export const enableUseMutableSource = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 15a8c29f71799..b61bbcf15747c 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -74,6 +74,7 @@ export const enableSyncDefaultUpdates = true; export const allowConcurrentByDefault = true; export const consoleManagedByDevToolsDuringStrictMode = false; +export const enableUseMutableSource = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 3c11070d6ecb3..7e24f2a0a2710 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -66,6 +66,7 @@ export const allowConcurrentByDefault = false; export const enablePersistentOffscreenHostContainer = false; export const consoleManagedByDevToolsDuringStrictMode = false; +export const enableUseMutableSource = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 4b0457c219587..bce24128a7911 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -66,6 +66,7 @@ export const allowConcurrentByDefault = false; export const enablePersistentOffscreenHostContainer = false; export const consoleManagedByDevToolsDuringStrictMode = false; +export const enableUseMutableSource = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 2c54c1fb77c20..02f063e58a822 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -65,6 +65,7 @@ export const allowConcurrentByDefault = true; export const enablePersistentOffscreenHostContainer = false; export const consoleManagedByDevToolsDuringStrictMode = false; +export const enableUseMutableSource = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 043ac7da254b9..7227c254dccbe 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -67,6 +67,9 @@ export const enablePersistentOffscreenHostContainer = false; export const consoleManagedByDevToolsDuringStrictMode = false; +// Some www surfaces are still using this. Remove once they have been migrated. +export const enableUseMutableSource = true; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index 76af047ab6d4e..264127fc197ef 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -66,6 +66,7 @@ export const allowConcurrentByDefault = false; export const enablePersistentOffscreenHostContainer = false; export const consoleManagedByDevToolsDuringStrictMode = false; +export const enableUseMutableSource = false; // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index 9eefdc2cca773..87d7247d6eebe 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -67,6 +67,9 @@ export const enablePersistentOffscreenHostContainer = false; export const consoleManagedByDevToolsDuringStrictMode = false; +// Some www surfaces are still using this. Remove once they have been migrated. +export const enableUseMutableSource = true; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 0f1ce6eb1f9f7..e280e1ff9a6d0 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -102,6 +102,9 @@ export const enablePersistentOffscreenHostContainer = false; export const consoleManagedByDevToolsDuringStrictMode = true; +// Some www surfaces are still using this. Remove once they have been migrated. +export const enableUseMutableSource = true; + // Flow magic to verify the exports of this file match the original version. // eslint-disable-next-line no-unused-vars type Check<_X, Y: _X, X: Y = _X> = null;