Delete useMutableSource implementation (#22292)
This API was replaced by useSyncExternalStore
Andrew Clark committed
Sep 20, 2021 at 00:11 UTC
8209de269531767b33d8db26eda41db38bfb6a27
37 files changed
+14
-3907
packages/react-debug-tools/src/ReactDebugHooks.js
+1
-25
@@ -7,13 +7,7 @@
7
* @flow
8
*/
9
10
-import type {
11
- MutableSource,
12
- MutableSourceGetSnapshotFn,
13
- MutableSourceSubscribeFn,
14
- ReactContext,
15
- ReactProviderType,
16
-} from 'shared/ReactTypes';
10
+import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
11
import type {
12
Fiber,
13
Dispatcher as DispatcherType,
@@ -261,23 +255,6 @@ function useMemo<T>(
255
return value;
256
}
257
264
-function useMutableSource<Source, Snapshot>(
265
- source: MutableSource<Source>,
266
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
267
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
268
-): Snapshot {
269
- // useMutableSource() composes multiple hooks internally.
270
- // Advance the current hook index the same number of times
271
- // so that subsequent hooks have the right memoized state.
272
- nextHook(); // MutableSource
273
- nextHook(); // State
274
- nextHook(); // Effect
275
- nextHook(); // Effect
276
- const value = getSnapshot(source._source);
277
- hookLog.push({primitive: 'MutableSource', stackError: new Error(), value});
278
- return value;
279
-}
280
-
258
function useSyncExternalStore<T>(
259
subscribe: (() => void) => () => void,
260
getSnapshot: () => T,
@@ -357,7 +334,6 @@ const Dispatcher: DispatcherType = {
334
useRef,
335
useState,
336
useTransition,
360
- useMutableSource,
337
useSyncExternalStore,
338
useDeferredValue,
339
useOpaqueIdentifier,
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
-37
@@ -1019,43 +1019,6 @@ describe('ReactHooksInspectionIntegration', () => {
1019
]);
1020
});
1021
1022
- it('should support composite useMutableSource hook', () => {
1023
- const createMutableSource =
1024
- React.createMutableSource || React.unstable_createMutableSource;
1025
- const useMutableSource =
1026
- React.useMutableSource || React.unstable_useMutableSource;
1027
-
1028
- const mutableSource = createMutableSource({}, () => 1);
1029
- function Foo(props) {
1030
- useMutableSource(
1031
- mutableSource,
1032
- () => 'snapshot',
1033
- () => {},
1034
- );
1035
- React.useMemo(() => 'memo', []);
1036
- return <div />;
1037
- }
1038
- const renderer = ReactTestRenderer.create(<Foo />);
1039
- const childFiber = renderer.root.findByType(Foo)._currentFiber();
1040
- const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
1041
- expect(tree).toEqual([
1042
- {
1043
- id: 0,
1044
- isStateEditable: false,
1045
- name: 'MutableSource',
1046
- value: 'snapshot',
1047
- subHooks: [],
1048
- },
1049
- {
1050
- id: 1,
1051
- isStateEditable: false,
1052
- name: 'Memo',
1053
- value: 'memo',
1054
- subHooks: [],
1055
- },
1056
- ]);
1057
- });
1058
-
1022
// @gate experimental || www
1023
it('should support composite useSyncExternalStore hook', () => {
1024
const useSyncExternalStore = React.unstable_useSyncExternalStore;
packages/react-dom/src/client/ReactDOMRoot.js
+1
-26
@@ -8,7 +8,7 @@
8
*/
9
10
import type {Container} from './ReactDOMHostConfig';
11
-import type {MutableSource, ReactNodeList} from 'shared/ReactTypes';
11
+import type {ReactNodeList} from 'shared/ReactTypes';
12
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
13
14
export type RootType = {
@@ -24,7 +24,6 @@ export type CreateRootOptions = {
24
hydrationOptions?: {
25
onHydrated?: (suspenseNode: Comment) => void,
26
onDeleted?: (suspenseNode: Comment) => void,
27
- mutableSources?: Array<MutableSource<any>>,
27
...
28
},
29
// END OF TODO
@@ -35,7 +34,6 @@ export type CreateRootOptions = {
34
35
export type HydrateRootOptions = {
36
// Hydration options
38
- hydratedSources?: Array<MutableSource<any>>,
37
onHydrated?: (suspenseNode: Comment) => void,
38
onDeleted?: (suspenseNode: Comment) => void,
39
// Options for all roots
@@ -61,7 +59,6 @@ import {
59
createContainer,
60
updateContainer,
61
findHostInstanceWithNoPortals,
64
- registerMutableSourceForHydration,
62
} from 'react-reconciler/src/ReactFiberReconciler';
63
import invariant from 'shared/invariant';
64
import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
@@ -129,11 +126,6 @@ export function createRoot(
126
const hydrate = options != null && options.hydrate === true;
127
const hydrationCallbacks =
128
(options != null && options.hydrationOptions) || null;
132
- const mutableSources =
133
- (options != null &&
134
- options.hydrationOptions != null &&
135
- options.hydrationOptions.mutableSources) ||
136
- null;
129
// END TODO
130
131
const isStrictMode = options != null && options.unstable_strictMode === true;
@@ -159,15 +151,6 @@ export function createRoot(
151
container.nodeType === COMMENT_NODE ? container.parentNode : container;
152
listenToAllSupportedEvents(rootContainerElement);
153
162
- // TODO: Delete this path
163
- if (mutableSources) {
164
- for (let i = 0; i < mutableSources.length; i++) {
165
- const mutableSource = mutableSources[i];
166
- registerMutableSourceForHydration(root, mutableSource);
167
- }
168
- }
169
- // END TODO
170
-
154
return new ReactDOMRoot(root);
155
}
156
@@ -185,7 +168,6 @@ export function hydrateRoot(
168
// For now we reuse the whole bag of options since they contain
169
// the hydration callbacks.
170
const hydrationCallbacks = options != null ? options : null;
188
- const mutableSources = (options != null && options.hydratedSources) || null;
171
const isStrictMode = options != null && options.unstable_strictMode === true;
172
173
let concurrentUpdatesByDefaultOverride = null;
@@ -208,13 +190,6 @@ export function hydrateRoot(
190
// This can't be a comment node since hydration doesn't work on comment nodes anyway.
191
listenToAllSupportedEvents(container);
192
211
- if (mutableSources) {
212
- for (let i = 0; i < mutableSources.length; i++) {
213
- const mutableSource = mutableSources[i];
214
- registerMutableSourceForHydration(root, mutableSource);
215
- }
216
- }
217
-
193
// Render the initial children
194
updateContainer(initialChildren, root, null, null);
195
packages/react-dom/src/server/ReactPartialRendererHooks.js
+1
-20
@@ -9,12 +9,7 @@
9
10
import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
11
12
-import type {
13
- MutableSource,
14
- MutableSourceGetSnapshotFn,
15
- MutableSourceSubscribeFn,
16
- ReactContext,
17
-} from 'shared/ReactTypes';
12
+import type {ReactContext} from 'shared/ReactTypes';
13
import type PartialRenderer from './ReactPartialRenderer';
14
15
import {validateContextBounds} from './ReactPartialRendererContext';
@@ -466,18 +461,6 @@ export function useCallback<T>(
461
return useMemo(() => callback, deps);
462
}
463
469
-// TODO Decide on how to implement this hook for server rendering.
470
-// If a mutation occurs during render, consider triggering a Suspense boundary
471
-// and falling back to client rendering.
472
-function useMutableSource<Source, Snapshot>(
473
- source: MutableSource<Source>,
474
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
475
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
476
-): Snapshot {
477
- resolveCurrentlyRenderingComponent();
478
- return getSnapshot(source._source);
479
-}
480
-
464
function useSyncExternalStore<T>(
465
subscribe: (() => void) => () => void,
466
getSnapshot: () => T,
@@ -536,8 +519,6 @@ export const Dispatcher: DispatcherType = {
519
useDeferredValue,
520
useTransition,
521
useOpaqueIdentifier,
539
- // Subscriptions are not setup in a server environment.
540
- useMutableSource,
522
useSyncExternalStore,
523
};
524
packages/react-reconciler/src/ReactFiberBeginWork.new.js
-18
@@ -12,7 +12,6 @@ import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
12
import type {Fiber, FiberRoot} from './ReactInternalTypes';
13
import type {TypeOfMode} from './ReactTypeOfMode';
14
import type {Lanes, Lane} from './ReactFiberLane.new';
15
-import type {MutableSource} from 'shared/ReactTypes';
15
import type {
16
SuspenseState,
17
SuspenseListRenderState,
@@ -145,7 +144,6 @@ import {
144
isSuspenseInstancePending,
145
isSuspenseInstanceFallback,
146
registerSuspenseInstanceRetry,
148
- supportsHydration,
147
isPrimaryRenderer,
148
supportsPersistence,
149
getOffscreenContainerProps,
@@ -224,7 +222,6 @@ import {
222
RetryAfterError,
223
NoContext,
224
} from './ReactFiberWorkLoop.new';
227
-import {setWorkInProgressVersion} from './ReactMutableSource.new';
225
import {
226
requestCacheFromPool,
227
pushCacheProvider,
@@ -1303,21 +1300,6 @@ function updateHostRoot(current, workInProgress, renderLanes) {
1300
// We always try to hydrate. If this isn't a hydration pass there won't
1301
// be any children to hydrate which is effectively the same thing as
1302
// not hydrating.
1306
-
1307
- if (supportsHydration) {
1308
- const mutableSourceEagerHydrationData =
1309
- root.mutableSourceEagerHydrationData;
1310
- if (mutableSourceEagerHydrationData != null) {
1311
- for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
1312
- const mutableSource = ((mutableSourceEagerHydrationData[
1313
- i
1314
- ]: any): MutableSource<any>);
1315
- const version = mutableSourceEagerHydrationData[i + 1];
1316
- setWorkInProgressVersion(mutableSource, version);
1317
- }
1318
- }
1319
- }
1320
-
1303
const child = mountChildFibers(
1304
workInProgress,
1305
null,
packages/react-reconciler/src/ReactFiberBeginWork.old.js
-18
@@ -12,7 +12,6 @@ import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
12
import type {Fiber, FiberRoot} from './ReactInternalTypes';
13
import type {TypeOfMode} from './ReactTypeOfMode';
14
import type {Lanes, Lane} from './ReactFiberLane.old';
15
-import type {MutableSource} from 'shared/ReactTypes';
15
import type {
16
SuspenseState,
17
SuspenseListRenderState,
@@ -145,7 +144,6 @@ import {
144
isSuspenseInstancePending,
145
isSuspenseInstanceFallback,
146
registerSuspenseInstanceRetry,
148
- supportsHydration,
147
isPrimaryRenderer,
148
supportsPersistence,
149
getOffscreenContainerProps,
@@ -224,7 +222,6 @@ import {
222
RetryAfterError,
223
NoContext,
224
} from './ReactFiberWorkLoop.old';
227
-import {setWorkInProgressVersion} from './ReactMutableSource.old';
225
import {
226
requestCacheFromPool,
227
pushCacheProvider,
@@ -1303,21 +1300,6 @@ function updateHostRoot(current, workInProgress, renderLanes) {
1300
// We always try to hydrate. If this isn't a hydration pass there won't
1301
// be any children to hydrate which is effectively the same thing as
1302
// not hydrating.
1306
-
1307
- if (supportsHydration) {
1308
- const mutableSourceEagerHydrationData =
1309
- root.mutableSourceEagerHydrationData;
1310
- if (mutableSourceEagerHydrationData != null) {
1311
- for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
1312
- const mutableSource = ((mutableSourceEagerHydrationData[
1313
- i
1314
- ]: any): MutableSource<any>);
1315
- const version = mutableSourceEagerHydrationData[i + 1];
1316
- setWorkInProgressVersion(mutableSource, version);
1317
- }
1318
- }
1319
- }
1320
-
1303
const child = mountChildFibers(
1304
workInProgress,
1305
null,
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
-3
@@ -30,8 +30,6 @@ import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
30
import type {OffscreenState} from './ReactFiberOffscreenComponent';
31
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.new';
32
33
-import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
34
-
33
import {now} from './Scheduler';
34
35
import {
@@ -854,7 +852,6 @@ function completeWork(
852
}
853
popHostContainer(workInProgress);
854
popTopLevelLegacyContextObject(workInProgress);
857
- resetMutableSourceWorkInProgressVersions();
855
if (fiberRoot.pendingContext) {
856
fiberRoot.context = fiberRoot.pendingContext;
857
fiberRoot.pendingContext = null;
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
-3
@@ -30,8 +30,6 @@ import type {SuspenseContext} from './ReactFiberSuspenseContext.old';
30
import type {OffscreenState} from './ReactFiberOffscreenComponent';
31
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old';
32
33
-import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.old';
34
-
33
import {now} from './Scheduler';
34
35
import {
@@ -854,7 +852,6 @@ function completeWork(
852
}
853
popHostContainer(workInProgress);
854
popTopLevelLegacyContextObject(workInProgress);
857
- resetMutableSourceWorkInProgressVersions();
855
if (fiberRoot.pendingContext) {
856
fiberRoot.context = fiberRoot.pendingContext;
857
fiberRoot.pendingContext = null;
packages/react-reconciler/src/ReactFiberHooks.new.js
+1
-400
@@ -7,12 +7,7 @@
7
* @flow
8
*/
9
10
-import type {
11
- MutableSource,
12
- MutableSourceGetSnapshotFn,
13
- MutableSourceSubscribeFn,
14
- ReactContext,
15
-} from 'shared/ReactTypes';
10
+import type {ReactContext} from 'shared/ReactTypes';
11
import type {Fiber, Dispatcher, HookType} from './ReactInternalTypes';
12
import type {Lanes, Lane} from './ReactFiberLane.new';
13
import type {HookFlags} from './ReactHookEffectTags';
@@ -50,7 +45,6 @@ import {
45
intersectLanes,
46
isTransitionLane,
47
markRootEntangled,
53
- markRootMutableRead,
48
NoTimestamp,
49
} from './ReactFiberLane.new';
50
import {
@@ -102,12 +96,6 @@ import {
96
makeClientIdInDEV,
97
makeOpaqueHydratingObject,
98
} from './ReactFiberHostConfig';
105
-import {
106
- getWorkInProgressVersion,
107
- markSourceAsDirty,
108
- setWorkInProgressVersion,
109
- warnAboutMultipleRenderersDEV,
110
-} from './ReactMutableSource.new';
99
import {getIsRendering} from './ReactCurrentFiber';
100
import {logStateUpdateScheduled} from './DebugTracing';
101
import {markStateUpdateScheduled} from './SchedulingProfiler';
@@ -118,7 +106,6 @@ import {
106
entangleTransitions,
107
} from './ReactUpdateQueue.new';
108
import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.new';
121
-import {getIsStrictModeForDevtools} from './ReactFiberReconciler.new';
109
import {warnOnSubscriptionInsideStartTransition} from 'shared/ReactFeatureFlags';
110
111
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
@@ -948,322 +935,6 @@ function rerenderReducer<S, I, A>(
935
return [newState, dispatch];
936
}
937
951
-type MutableSourceMemoizedState<Source, Snapshot> = {|
952
- refs: {
953
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
954
- setSnapshot: Snapshot => void,
955
- },
956
- source: MutableSource<any>,
957
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
958
-|};
959
-
960
-function readFromUnsubscribedMutableSource<Source, Snapshot>(
961
- root: FiberRoot,
962
- source: MutableSource<Source>,
963
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
964
-): Snapshot {
965
- if (__DEV__) {
966
- warnAboutMultipleRenderersDEV(source);
967
- }
968
-
969
- const getVersion = source._getVersion;
970
- const version = getVersion(source._source);
971
-
972
- // Is it safe for this component to read from this source during the current render?
973
- let isSafeToReadFromSource = false;
974
-
975
- // Check the version first.
976
- // If this render has already been started with a specific version,
977
- // we can use it alone to determine if we can safely read from the source.
978
- const currentRenderVersion = getWorkInProgressVersion(source);
979
- if (currentRenderVersion !== null) {
980
- // It's safe to read if the store hasn't been mutated since the last time
981
- // we read something.
982
- isSafeToReadFromSource = currentRenderVersion === version;
983
- } else {
984
- // If there's no version, then this is the first time we've read from the
985
- // source during the current render pass, so we need to do a bit more work.
986
- // What we need to determine is if there are any hooks that already
987
- // subscribed to the source, and if so, whether there are any pending
988
- // mutations that haven't been synchronized yet.
989
- //
990
- // If there are no pending mutations, then `root.mutableReadLanes` will be
991
- // empty, and we know we can safely read.
992
- //
993
- // If there *are* pending mutations, we may still be able to safely read
994
- // if the currently rendering lanes are inclusive of the pending mutation
995
- // lanes, since that guarantees that the value we're about to read from
996
- // the source is consistent with the values that we read during the most
997
- // recent mutation.
998
- isSafeToReadFromSource = isSubsetOfLanes(
999
- renderLanes,
1000
- root.mutableReadLanes,
1001
- );
1002
-
1003
- if (isSafeToReadFromSource) {
1004
- // If it's safe to read from this source during the current render,
1005
- // store the version in case other components read from it.
1006
- // A changed version number will let those components know to throw and restart the render.
1007
- setWorkInProgressVersion(source, version);
1008
- }
1009
- }
1010
-
1011
- if (isSafeToReadFromSource) {
1012
- const snapshot = getSnapshot(source._source);
1013
- if (__DEV__) {
1014
- if (typeof snapshot === 'function') {
1015
- console.error(
1016
- 'Mutable source should not return a function as the snapshot value. ' +
1017
- 'Functions may close over mutable values and cause tearing.',
1018
- );
1019
- }
1020
- }
1021
- return snapshot;
1022
- } else {
1023
- // This handles the special case of a mutable source being shared between renderers.
1024
- // In that case, if the source is mutated between the first and second renderer,
1025
- // The second renderer don't know that it needs to reset the WIP version during unwind,
1026
- // (because the hook only marks sources as dirty if it's written to their WIP version).
1027
- // That would cause this tear check to throw again and eventually be visible to the user.
1028
- // We can avoid this infinite loop by explicitly marking the source as dirty.
1029
- //
1030
- // This can lead to tearing in the first renderer when it resumes,
1031
- // but there's nothing we can do about that (short of throwing here and refusing to continue the render).
1032
- markSourceAsDirty(source);
1033
-
1034
- // Intentioally throw an error to force React to retry synchronously. During
1035
- // the synchronous retry, it will block interleaved mutations, so we should
1036
- // get a consistent read. Therefore, the following error should never be
1037
- // visible to the user.
1038
- //
1039
- // If it were to become visible to the user, it suggests one of two things:
1040
- // a bug in React, or (more likely), a mutation during the render phase that
1041
- // caused the second re-render attempt to be different from the first.
1042
- //
1043
- // We know it's the second case if the logs are currently disabled. So in
1044
- // dev, we can present a more accurate error message.
1045
- if (__DEV__) {
1046
- // eslint-disable-next-line react-internal/no-production-logging
1047
- if (getIsStrictModeForDevtools()) {
1048
- // If getIsStrictModeForDevtools is true, this is the dev-only double render
1049
- // This is only reachable if there was a mutation during render. Show a helpful
1050
- // error message.
1051
- //
1052
- // Something interesting to note: because we only double render in
1053
- // development, this error will never happen during production. This is
1054
- // actually true of all errors that occur during a double render,
1055
- // because if the first render had thrown, we would have exited the
1056
- // begin phase without double rendering. We should consider suppressing
1057
- // any error from a double render (with a warning) to more closely match
1058
- // the production behavior.
1059
- const componentName = getComponentNameFromFiber(
1060
- currentlyRenderingFiber,
1061
- );
1062
- invariant(
1063
- false,
1064
- 'A mutable source was mutated while the %s component was rendering. ' +
1065
- 'This is not supported. Move any mutations into event handlers ' +
1066
- 'or effects.',
1067
- componentName,
1068
- );
1069
- }
1070
- }
1071
-
1072
- // We expect this error not to be thrown during the synchronous retry,
1073
- // because we blocked interleaved mutations.
1074
- invariant(
1075
- false,
1076
- 'Cannot read from mutable source during the current render without tearing. This may be a bug in React. Please file an issue.',
1077
- );
1078
- }
1079
-}
1080
-
1081
-function useMutableSource<Source, Snapshot>(
1082
- hook: Hook,
1083
- source: MutableSource<Source>,
1084
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1085
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1086
-): Snapshot {
1087
- const root = ((getWorkInProgressRoot(): any): FiberRoot);
1088
- invariant(
1089
- root !== null,
1090
- 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
1091
- );
1092
-
1093
- const getVersion = source._getVersion;
1094
- const version = getVersion(source._source);
1095
-
1096
- const dispatcher = ReactCurrentDispatcher.current;
1097
-
1098
- // eslint-disable-next-line prefer-const
1099
- let [currentSnapshot, setSnapshot] = dispatcher.useState(() =>
1100
- readFromUnsubscribedMutableSource(root, source, getSnapshot),
1101
- );
1102
- let snapshot = currentSnapshot;
1103
-
1104
- // Grab a handle to the state hook as well.
1105
- // We use it to clear the pending update queue if we have a new source.
1106
- const stateHook = ((workInProgressHook: any): Hook);
1107
-
1108
- const memoizedState = ((hook.memoizedState: any): MutableSourceMemoizedState<
1109
- Source,
1110
- Snapshot,
1111
- >);
1112
- const refs = memoizedState.refs;
1113
- const prevGetSnapshot = refs.getSnapshot;
1114
- const prevSource = memoizedState.source;
1115
- const prevSubscribe = memoizedState.subscribe;
1116
-
1117
- const fiber = currentlyRenderingFiber;
1118
-
1119
- hook.memoizedState = ({
1120
- refs,
1121
- source,
1122
- subscribe,
1123
- }: MutableSourceMemoizedState<Source, Snapshot>);
1124
-
1125
- // Sync the values needed by our subscription handler after each commit.
1126
- dispatcher.useEffect(() => {
1127
- refs.getSnapshot = getSnapshot;
1128
-
1129
- // Normally the dispatch function for a state hook never changes,
1130
- // but this hook recreates the queue in certain cases to avoid updates from stale sources.
1131
- // handleChange() below needs to reference the dispatch function without re-subscribing,
1132
- // so we use a ref to ensure that it always has the latest version.
1133
- refs.setSnapshot = setSnapshot;
1134
-
1135
- // Check for a possible change between when we last rendered now.
1136
- const maybeNewVersion = getVersion(source._source);
1137
- if (!is(version, maybeNewVersion)) {
1138
- const maybeNewSnapshot = getSnapshot(source._source);
1139
- if (__DEV__) {
1140
- if (typeof maybeNewSnapshot === 'function') {
1141
- console.error(
1142
- 'Mutable source should not return a function as the snapshot value. ' +
1143
- 'Functions may close over mutable values and cause tearing.',
1144
- );
1145
- }
1146
- }
1147
-
1148
- if (!is(snapshot, maybeNewSnapshot)) {
1149
- setSnapshot(maybeNewSnapshot);
1150
-
1151
- const lane = requestUpdateLane(fiber);
1152
- markRootMutableRead(root, lane);
1153
- }
1154
- // If the source mutated between render and now,
1155
- // there may be state updates already scheduled from the old source.
1156
- // Entangle the updates so that they render in the same batch.
1157
- markRootEntangled(root, root.mutableReadLanes);
1158
- }
1159
- }, [getSnapshot, source, subscribe]);
1160
-
1161
- // If we got a new source or subscribe function, re-subscribe in a passive effect.
1162
- dispatcher.useEffect(() => {
1163
- const handleChange = () => {
1164
- const latestGetSnapshot = refs.getSnapshot;
1165
- const latestSetSnapshot = refs.setSnapshot;
1166
-
1167
- try {
1168
- latestSetSnapshot(latestGetSnapshot(source._source));
1169
-
1170
- // Record a pending mutable source update with the same expiration time.
1171
- const lane = requestUpdateLane(fiber);
1172
-
1173
- markRootMutableRead(root, lane);
1174
- } catch (error) {
1175
- // A selector might throw after a source mutation.
1176
- // e.g. it might try to read from a part of the store that no longer exists.
1177
- // In this case we should still schedule an update with React.
1178
- // Worst case the selector will throw again and then an error boundary will handle it.
1179
- latestSetSnapshot(
1180
- (() => {
1181
- throw error;
1182
- }: any),
1183
- );
1184
- }
1185
- };
1186
-
1187
- const unsubscribe = subscribe(source._source, handleChange);
1188
- if (__DEV__) {
1189
- if (typeof unsubscribe !== 'function') {
1190
- console.error(
1191
- 'Mutable source subscribe function must return an unsubscribe function.',
1192
- );
1193
- }
1194
- }
1195
-
1196
- return unsubscribe;
1197
- }, [source, subscribe]);
1198
-
1199
- // If any of the inputs to useMutableSource change, reading is potentially unsafe.
1200
- //
1201
- // If either the source or the subscription have changed we can't can't trust the update queue.
1202
- // Maybe the source changed in a way that the old subscription ignored but the new one depends on.
1203
- //
1204
- // If the getSnapshot function changed, we also shouldn't rely on the update queue.
1205
- // It's possible that the underlying source was mutated between the when the last "change" event fired,
1206
- // and when the current render (with the new getSnapshot function) is processed.
1207
- //
1208
- // In both cases, we need to throw away pending updates (since they are no longer relevant)
1209
- // and treat reading from the source as we do in the mount case.
1210
- if (
1211
- !is(prevGetSnapshot, getSnapshot) ||
1212
- !is(prevSource, source) ||
1213
- !is(prevSubscribe, subscribe)
1214
- ) {
1215
- // Create a new queue and setState method,
1216
- // So if there are interleaved updates, they get pushed to the older queue.
1217
- // When this becomes current, the previous queue and dispatch method will be discarded,
1218
- // including any interleaving updates that occur.
1219
- const newQueue: UpdateQueue<Snapshot, BasicStateAction<Snapshot>> = {
1220
- pending: null,
1221
- interleaved: null,
1222
- lanes: NoLanes,
1223
- dispatch: null,
1224
- lastRenderedReducer: basicStateReducer,
1225
- lastRenderedState: snapshot,
1226
- };
1227
- newQueue.dispatch = setSnapshot = (dispatchAction.bind(
1228
- null,
1229
- currentlyRenderingFiber,
1230
- newQueue,
1231
- ): any);
1232
- stateHook.queue = newQueue;
1233
- stateHook.baseQueue = null;
1234
- snapshot = readFromUnsubscribedMutableSource(root, source, getSnapshot);
1235
- stateHook.memoizedState = stateHook.baseState = snapshot;
1236
- }
1237
-
1238
- return snapshot;
1239
-}
1240
-
1241
-function mountMutableSource<Source, Snapshot>(
1242
- source: MutableSource<Source>,
1243
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1244
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1245
-): Snapshot {
1246
- const hook = mountWorkInProgressHook();
1247
- hook.memoizedState = ({
1248
- refs: {
1249
- getSnapshot,
1250
- setSnapshot: (null: any),
1251
- },
1252
- source,
1253
- subscribe,
1254
- }: MutableSourceMemoizedState<Source, Snapshot>);
1255
- return useMutableSource(hook, source, getSnapshot, subscribe);
1256
-}
1257
-
1258
-function updateMutableSource<Source, Snapshot>(
1259
- source: MutableSource<Source>,
1260
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1261
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1262
-): Snapshot {
1263
- const hook = updateWorkInProgressHook();
1264
- return useMutableSource(hook, source, getSnapshot, subscribe);
1265
-}
1266
-
938
function mountSyncExternalStore<T>(
939
subscribe: (() => void) => () => void,
940
getSnapshot: () => T,
@@ -2334,7 +2005,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
2005
useDebugValue: throwInvalidHookError,
2006
useDeferredValue: throwInvalidHookError,
2007
useTransition: throwInvalidHookError,
2337
- useMutableSource: throwInvalidHookError,
2008
useSyncExternalStore: throwInvalidHookError,
2009
useOpaqueIdentifier: throwInvalidHookError,
2010
@@ -2361,7 +2031,6 @@ const HooksDispatcherOnMount: Dispatcher = {
2031
useDebugValue: mountDebugValue,
2032
useDeferredValue: mountDeferredValue,
2033
useTransition: mountTransition,
2364
- useMutableSource: mountMutableSource,
2034
useSyncExternalStore: mountSyncExternalStore,
2035
useOpaqueIdentifier: mountOpaqueIdentifier,
2036
@@ -2388,7 +2057,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
2057
useDebugValue: updateDebugValue,
2058
useDeferredValue: updateDeferredValue,
2059
useTransition: updateTransition,
2391
- useMutableSource: updateMutableSource,
2060
useSyncExternalStore: updateSyncExternalStore,
2061
useOpaqueIdentifier: updateOpaqueIdentifier,
2062
@@ -2415,7 +2083,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
2083
useDebugValue: updateDebugValue,
2084
useDeferredValue: rerenderDeferredValue,
2085
useTransition: rerenderTransition,
2418
- useMutableSource: updateMutableSource,
2086
useSyncExternalStore: mountSyncExternalStore,
2087
useOpaqueIdentifier: rerenderOpaqueIdentifier,
2088
@@ -2565,15 +2232,6 @@ if (__DEV__) {
2232
mountHookTypesDev();
2233
return mountTransition();
2234
},
2568
- useMutableSource<Source, Snapshot>(
2569
- source: MutableSource<Source>,
2570
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2571
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2572
- ): Snapshot {
2573
- currentHookNameInDev = 'useMutableSource';
2574
- mountHookTypesDev();
2575
- return mountMutableSource(source, getSnapshot, subscribe);
2576
- },
2235
useSyncExternalStore<T>(
2236
subscribe: (() => void) => () => void,
2237
getSnapshot: () => T,
@@ -2705,15 +2363,6 @@ if (__DEV__) {
2363
updateHookTypesDev();
2364
return mountTransition();
2365
},
2708
- useMutableSource<Source, Snapshot>(
2709
- source: MutableSource<Source>,
2710
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2711
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2712
- ): Snapshot {
2713
- currentHookNameInDev = 'useMutableSource';
2714
- updateHookTypesDev();
2715
- return mountMutableSource(source, getSnapshot, subscribe);
2716
- },
2366
useSyncExternalStore<T>(
2367
subscribe: (() => void) => () => void,
2368
getSnapshot: () => T,
@@ -2845,15 +2494,6 @@ if (__DEV__) {
2494
updateHookTypesDev();
2495
return updateTransition();
2496
},
2848
- useMutableSource<Source, Snapshot>(
2849
- source: MutableSource<Source>,
2850
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2851
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2852
- ): Snapshot {
2853
- currentHookNameInDev = 'useMutableSource';
2854
- updateHookTypesDev();
2855
- return updateMutableSource(source, getSnapshot, subscribe);
2856
- },
2497
useSyncExternalStore<T>(
2498
subscribe: (() => void) => () => void,
2499
getSnapshot: () => T,
@@ -2986,15 +2626,6 @@ if (__DEV__) {
2626
updateHookTypesDev();
2627
return rerenderTransition();
2628
},
2989
- useMutableSource<Source, Snapshot>(
2990
- source: MutableSource<Source>,
2991
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2992
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2993
- ): Snapshot {
2994
- currentHookNameInDev = 'useMutableSource';
2995
- updateHookTypesDev();
2996
- return updateMutableSource(source, getSnapshot, subscribe);
2997
- },
2629
useSyncExternalStore<T>(
2630
subscribe: (() => void) => () => void,
2631
getSnapshot: () => T,
@@ -3140,16 +2771,6 @@ if (__DEV__) {
2771
mountHookTypesDev();
2772
return mountTransition();
2773
},
3143
- useMutableSource<Source, Snapshot>(
3144
- source: MutableSource<Source>,
3145
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3146
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3147
- ): Snapshot {
3148
- currentHookNameInDev = 'useMutableSource';
3149
- warnInvalidHookAccess();
3150
- mountHookTypesDev();
3151
- return mountMutableSource(source, getSnapshot, subscribe);
3152
- },
2774
useSyncExternalStore<T>(
2775
subscribe: (() => void) => () => void,
2776
getSnapshot: () => T,
@@ -3297,16 +2918,6 @@ if (__DEV__) {
2918
updateHookTypesDev();
2919
return updateTransition();
2920
},
3300
- useMutableSource<Source, Snapshot>(
3301
- source: MutableSource<Source>,
3302
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3303
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3304
- ): Snapshot {
3305
- currentHookNameInDev = 'useMutableSource';
3306
- warnInvalidHookAccess();
3307
- updateHookTypesDev();
3308
- return updateMutableSource(source, getSnapshot, subscribe);
3309
- },
2921
useSyncExternalStore<T>(
2922
subscribe: (() => void) => () => void,
2923
getSnapshot: () => T,
@@ -3455,16 +3066,6 @@ if (__DEV__) {
3066
updateHookTypesDev();
3067
return rerenderTransition();
3068
},
3458
- useMutableSource<Source, Snapshot>(
3459
- source: MutableSource<Source>,
3460
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3461
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3462
- ): Snapshot {
3463
- currentHookNameInDev = 'useMutableSource';
3464
- warnInvalidHookAccess();
3465
- updateHookTypesDev();
3466
- return updateMutableSource(source, getSnapshot, subscribe);
3467
- },
3069
useSyncExternalStore<T>(
3070
subscribe: (() => void) => () => void,
3071
getSnapshot: () => T,
packages/react-reconciler/src/ReactFiberHooks.old.js
+1
-400
@@ -7,12 +7,7 @@
7
* @flow
8
*/
9
10
-import type {
11
- MutableSource,
12
- MutableSourceGetSnapshotFn,
13
- MutableSourceSubscribeFn,
14
- ReactContext,
15
-} from 'shared/ReactTypes';
10
+import type {ReactContext} from 'shared/ReactTypes';
11
import type {Fiber, Dispatcher, HookType} from './ReactInternalTypes';
12
import type {Lanes, Lane} from './ReactFiberLane.old';
13
import type {HookFlags} from './ReactHookEffectTags';
@@ -50,7 +45,6 @@ import {
45
intersectLanes,
46
isTransitionLane,
47
markRootEntangled,
53
- markRootMutableRead,
48
NoTimestamp,
49
} from './ReactFiberLane.old';
50
import {
@@ -102,12 +96,6 @@ import {
96
makeClientIdInDEV,
97
makeOpaqueHydratingObject,
98
} from './ReactFiberHostConfig';
105
-import {
106
- getWorkInProgressVersion,
107
- markSourceAsDirty,
108
- setWorkInProgressVersion,
109
- warnAboutMultipleRenderersDEV,
110
-} from './ReactMutableSource.old';
99
import {getIsRendering} from './ReactCurrentFiber';
100
import {logStateUpdateScheduled} from './DebugTracing';
101
import {markStateUpdateScheduled} from './SchedulingProfiler';
@@ -118,7 +106,6 @@ import {
106
entangleTransitions,
107
} from './ReactUpdateQueue.old';
108
import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old';
121
-import {getIsStrictModeForDevtools} from './ReactFiberReconciler.old';
109
import {warnOnSubscriptionInsideStartTransition} from 'shared/ReactFeatureFlags';
110
111
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
@@ -948,322 +935,6 @@ function rerenderReducer<S, I, A>(
935
return [newState, dispatch];
936
}
937
951
-type MutableSourceMemoizedState<Source, Snapshot> = {|
952
- refs: {
953
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
954
- setSnapshot: Snapshot => void,
955
- },
956
- source: MutableSource<any>,
957
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
958
-|};
959
-
960
-function readFromUnsubscribedMutableSource<Source, Snapshot>(
961
- root: FiberRoot,
962
- source: MutableSource<Source>,
963
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
964
-): Snapshot {
965
- if (__DEV__) {
966
- warnAboutMultipleRenderersDEV(source);
967
- }
968
-
969
- const getVersion = source._getVersion;
970
- const version = getVersion(source._source);
971
-
972
- // Is it safe for this component to read from this source during the current render?
973
- let isSafeToReadFromSource = false;
974
-
975
- // Check the version first.
976
- // If this render has already been started with a specific version,
977
- // we can use it alone to determine if we can safely read from the source.
978
- const currentRenderVersion = getWorkInProgressVersion(source);
979
- if (currentRenderVersion !== null) {
980
- // It's safe to read if the store hasn't been mutated since the last time
981
- // we read something.
982
- isSafeToReadFromSource = currentRenderVersion === version;
983
- } else {
984
- // If there's no version, then this is the first time we've read from the
985
- // source during the current render pass, so we need to do a bit more work.
986
- // What we need to determine is if there are any hooks that already
987
- // subscribed to the source, and if so, whether there are any pending
988
- // mutations that haven't been synchronized yet.
989
- //
990
- // If there are no pending mutations, then `root.mutableReadLanes` will be
991
- // empty, and we know we can safely read.
992
- //
993
- // If there *are* pending mutations, we may still be able to safely read
994
- // if the currently rendering lanes are inclusive of the pending mutation
995
- // lanes, since that guarantees that the value we're about to read from
996
- // the source is consistent with the values that we read during the most
997
- // recent mutation.
998
- isSafeToReadFromSource = isSubsetOfLanes(
999
- renderLanes,
1000
- root.mutableReadLanes,
1001
- );
1002
-
1003
- if (isSafeToReadFromSource) {
1004
- // If it's safe to read from this source during the current render,
1005
- // store the version in case other components read from it.
1006
- // A changed version number will let those components know to throw and restart the render.
1007
- setWorkInProgressVersion(source, version);
1008
- }
1009
- }
1010
-
1011
- if (isSafeToReadFromSource) {
1012
- const snapshot = getSnapshot(source._source);
1013
- if (__DEV__) {
1014
- if (typeof snapshot === 'function') {
1015
- console.error(
1016
- 'Mutable source should not return a function as the snapshot value. ' +
1017
- 'Functions may close over mutable values and cause tearing.',
1018
- );
1019
- }
1020
- }
1021
- return snapshot;
1022
- } else {
1023
- // This handles the special case of a mutable source being shared between renderers.
1024
- // In that case, if the source is mutated between the first and second renderer,
1025
- // The second renderer don't know that it needs to reset the WIP version during unwind,
1026
- // (because the hook only marks sources as dirty if it's written to their WIP version).
1027
- // That would cause this tear check to throw again and eventually be visible to the user.
1028
- // We can avoid this infinite loop by explicitly marking the source as dirty.
1029
- //
1030
- // This can lead to tearing in the first renderer when it resumes,
1031
- // but there's nothing we can do about that (short of throwing here and refusing to continue the render).
1032
- markSourceAsDirty(source);
1033
-
1034
- // Intentioally throw an error to force React to retry synchronously. During
1035
- // the synchronous retry, it will block interleaved mutations, so we should
1036
- // get a consistent read. Therefore, the following error should never be
1037
- // visible to the user.
1038
- //
1039
- // If it were to become visible to the user, it suggests one of two things:
1040
- // a bug in React, or (more likely), a mutation during the render phase that
1041
- // caused the second re-render attempt to be different from the first.
1042
- //
1043
- // We know it's the second case if the logs are currently disabled. So in
1044
- // dev, we can present a more accurate error message.
1045
- if (__DEV__) {
1046
- // eslint-disable-next-line react-internal/no-production-logging
1047
- if (getIsStrictModeForDevtools()) {
1048
- // If getIsStrictModeForDevtools is true, this is the dev-only double render
1049
- // This is only reachable if there was a mutation during render. Show a helpful
1050
- // error message.
1051
- //
1052
- // Something interesting to note: because we only double render in
1053
- // development, this error will never happen during production. This is
1054
- // actually true of all errors that occur during a double render,
1055
- // because if the first render had thrown, we would have exited the
1056
- // begin phase without double rendering. We should consider suppressing
1057
- // any error from a double render (with a warning) to more closely match
1058
- // the production behavior.
1059
- const componentName = getComponentNameFromFiber(
1060
- currentlyRenderingFiber,
1061
- );
1062
- invariant(
1063
- false,
1064
- 'A mutable source was mutated while the %s component was rendering. ' +
1065
- 'This is not supported. Move any mutations into event handlers ' +
1066
- 'or effects.',
1067
- componentName,
1068
- );
1069
- }
1070
- }
1071
-
1072
- // We expect this error not to be thrown during the synchronous retry,
1073
- // because we blocked interleaved mutations.
1074
- invariant(
1075
- false,
1076
- 'Cannot read from mutable source during the current render without tearing. This may be a bug in React. Please file an issue.',
1077
- );
1078
- }
1079
-}
1080
-
1081
-function useMutableSource<Source, Snapshot>(
1082
- hook: Hook,
1083
- source: MutableSource<Source>,
1084
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1085
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1086
-): Snapshot {
1087
- const root = ((getWorkInProgressRoot(): any): FiberRoot);
1088
- invariant(
1089
- root !== null,
1090
- 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
1091
- );
1092
-
1093
- const getVersion = source._getVersion;
1094
- const version = getVersion(source._source);
1095
-
1096
- const dispatcher = ReactCurrentDispatcher.current;
1097
-
1098
- // eslint-disable-next-line prefer-const
1099
- let [currentSnapshot, setSnapshot] = dispatcher.useState(() =>
1100
- readFromUnsubscribedMutableSource(root, source, getSnapshot),
1101
- );
1102
- let snapshot = currentSnapshot;
1103
-
1104
- // Grab a handle to the state hook as well.
1105
- // We use it to clear the pending update queue if we have a new source.
1106
- const stateHook = ((workInProgressHook: any): Hook);
1107
-
1108
- const memoizedState = ((hook.memoizedState: any): MutableSourceMemoizedState<
1109
- Source,
1110
- Snapshot,
1111
- >);
1112
- const refs = memoizedState.refs;
1113
- const prevGetSnapshot = refs.getSnapshot;
1114
- const prevSource = memoizedState.source;
1115
- const prevSubscribe = memoizedState.subscribe;
1116
-
1117
- const fiber = currentlyRenderingFiber;
1118
-
1119
- hook.memoizedState = ({
1120
- refs,
1121
- source,
1122
- subscribe,
1123
- }: MutableSourceMemoizedState<Source, Snapshot>);
1124
-
1125
- // Sync the values needed by our subscription handler after each commit.
1126
- dispatcher.useEffect(() => {
1127
- refs.getSnapshot = getSnapshot;
1128
-
1129
- // Normally the dispatch function for a state hook never changes,
1130
- // but this hook recreates the queue in certain cases to avoid updates from stale sources.
1131
- // handleChange() below needs to reference the dispatch function without re-subscribing,
1132
- // so we use a ref to ensure that it always has the latest version.
1133
- refs.setSnapshot = setSnapshot;
1134
-
1135
- // Check for a possible change between when we last rendered now.
1136
- const maybeNewVersion = getVersion(source._source);
1137
- if (!is(version, maybeNewVersion)) {
1138
- const maybeNewSnapshot = getSnapshot(source._source);
1139
- if (__DEV__) {
1140
- if (typeof maybeNewSnapshot === 'function') {
1141
- console.error(
1142
- 'Mutable source should not return a function as the snapshot value. ' +
1143
- 'Functions may close over mutable values and cause tearing.',
1144
- );
1145
- }
1146
- }
1147
-
1148
- if (!is(snapshot, maybeNewSnapshot)) {
1149
- setSnapshot(maybeNewSnapshot);
1150
-
1151
- const lane = requestUpdateLane(fiber);
1152
- markRootMutableRead(root, lane);
1153
- }
1154
- // If the source mutated between render and now,
1155
- // there may be state updates already scheduled from the old source.
1156
- // Entangle the updates so that they render in the same batch.
1157
- markRootEntangled(root, root.mutableReadLanes);
1158
- }
1159
- }, [getSnapshot, source, subscribe]);
1160
-
1161
- // If we got a new source or subscribe function, re-subscribe in a passive effect.
1162
- dispatcher.useEffect(() => {
1163
- const handleChange = () => {
1164
- const latestGetSnapshot = refs.getSnapshot;
1165
- const latestSetSnapshot = refs.setSnapshot;
1166
-
1167
- try {
1168
- latestSetSnapshot(latestGetSnapshot(source._source));
1169
-
1170
- // Record a pending mutable source update with the same expiration time.
1171
- const lane = requestUpdateLane(fiber);
1172
-
1173
- markRootMutableRead(root, lane);
1174
- } catch (error) {
1175
- // A selector might throw after a source mutation.
1176
- // e.g. it might try to read from a part of the store that no longer exists.
1177
- // In this case we should still schedule an update with React.
1178
- // Worst case the selector will throw again and then an error boundary will handle it.
1179
- latestSetSnapshot(
1180
- (() => {
1181
- throw error;
1182
- }: any),
1183
- );
1184
- }
1185
- };
1186
-
1187
- const unsubscribe = subscribe(source._source, handleChange);
1188
- if (__DEV__) {
1189
- if (typeof unsubscribe !== 'function') {
1190
- console.error(
1191
- 'Mutable source subscribe function must return an unsubscribe function.',
1192
- );
1193
- }
1194
- }
1195
-
1196
- return unsubscribe;
1197
- }, [source, subscribe]);
1198
-
1199
- // If any of the inputs to useMutableSource change, reading is potentially unsafe.
1200
- //
1201
- // If either the source or the subscription have changed we can't can't trust the update queue.
1202
- // Maybe the source changed in a way that the old subscription ignored but the new one depends on.
1203
- //
1204
- // If the getSnapshot function changed, we also shouldn't rely on the update queue.
1205
- // It's possible that the underlying source was mutated between the when the last "change" event fired,
1206
- // and when the current render (with the new getSnapshot function) is processed.
1207
- //
1208
- // In both cases, we need to throw away pending updates (since they are no longer relevant)
1209
- // and treat reading from the source as we do in the mount case.
1210
- if (
1211
- !is(prevGetSnapshot, getSnapshot) ||
1212
- !is(prevSource, source) ||
1213
- !is(prevSubscribe, subscribe)
1214
- ) {
1215
- // Create a new queue and setState method,
1216
- // So if there are interleaved updates, they get pushed to the older queue.
1217
- // When this becomes current, the previous queue and dispatch method will be discarded,
1218
- // including any interleaving updates that occur.
1219
- const newQueue: UpdateQueue<Snapshot, BasicStateAction<Snapshot>> = {
1220
- pending: null,
1221
- interleaved: null,
1222
- lanes: NoLanes,
1223
- dispatch: null,
1224
- lastRenderedReducer: basicStateReducer,
1225
- lastRenderedState: snapshot,
1226
- };
1227
- newQueue.dispatch = setSnapshot = (dispatchAction.bind(
1228
- null,
1229
- currentlyRenderingFiber,
1230
- newQueue,
1231
- ): any);
1232
- stateHook.queue = newQueue;
1233
- stateHook.baseQueue = null;
1234
- snapshot = readFromUnsubscribedMutableSource(root, source, getSnapshot);
1235
- stateHook.memoizedState = stateHook.baseState = snapshot;
1236
- }
1237
-
1238
- return snapshot;
1239
-}
1240
-
1241
-function mountMutableSource<Source, Snapshot>(
1242
- source: MutableSource<Source>,
1243
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1244
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1245
-): Snapshot {
1246
- const hook = mountWorkInProgressHook();
1247
- hook.memoizedState = ({
1248
- refs: {
1249
- getSnapshot,
1250
- setSnapshot: (null: any),
1251
- },
1252
- source,
1253
- subscribe,
1254
- }: MutableSourceMemoizedState<Source, Snapshot>);
1255
- return useMutableSource(hook, source, getSnapshot, subscribe);
1256
-}
1257
-
1258
-function updateMutableSource<Source, Snapshot>(
1259
- source: MutableSource<Source>,
1260
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1261
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1262
-): Snapshot {
1263
- const hook = updateWorkInProgressHook();
1264
- return useMutableSource(hook, source, getSnapshot, subscribe);
1265
-}
1266
-
938
function mountSyncExternalStore<T>(
939
subscribe: (() => void) => () => void,
940
getSnapshot: () => T,
@@ -2334,7 +2005,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
2005
useDebugValue: throwInvalidHookError,
2006
useDeferredValue: throwInvalidHookError,
2007
useTransition: throwInvalidHookError,
2337
- useMutableSource: throwInvalidHookError,
2008
useSyncExternalStore: throwInvalidHookError,
2009
useOpaqueIdentifier: throwInvalidHookError,
2010
@@ -2361,7 +2031,6 @@ const HooksDispatcherOnMount: Dispatcher = {
2031
useDebugValue: mountDebugValue,
2032
useDeferredValue: mountDeferredValue,
2033
useTransition: mountTransition,
2364
- useMutableSource: mountMutableSource,
2034
useSyncExternalStore: mountSyncExternalStore,
2035
useOpaqueIdentifier: mountOpaqueIdentifier,
2036
@@ -2388,7 +2057,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
2057
useDebugValue: updateDebugValue,
2058
useDeferredValue: updateDeferredValue,
2059
useTransition: updateTransition,
2391
- useMutableSource: updateMutableSource,
2060
useSyncExternalStore: updateSyncExternalStore,
2061
useOpaqueIdentifier: updateOpaqueIdentifier,
2062
@@ -2415,7 +2083,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
2083
useDebugValue: updateDebugValue,
2084
useDeferredValue: rerenderDeferredValue,
2085
useTransition: rerenderTransition,
2418
- useMutableSource: updateMutableSource,
2086
useSyncExternalStore: mountSyncExternalStore,
2087
useOpaqueIdentifier: rerenderOpaqueIdentifier,
2088
@@ -2565,15 +2232,6 @@ if (__DEV__) {
2232
mountHookTypesDev();
2233
return mountTransition();
2234
},
2568
- useMutableSource<Source, Snapshot>(
2569
- source: MutableSource<Source>,
2570
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2571
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2572
- ): Snapshot {
2573
- currentHookNameInDev = 'useMutableSource';
2574
- mountHookTypesDev();
2575
- return mountMutableSource(source, getSnapshot, subscribe);
2576
- },
2235
useSyncExternalStore<T>(
2236
subscribe: (() => void) => () => void,
2237
getSnapshot: () => T,
@@ -2705,15 +2363,6 @@ if (__DEV__) {
2363
updateHookTypesDev();
2364
return mountTransition();
2365
},
2708
- useMutableSource<Source, Snapshot>(
2709
- source: MutableSource<Source>,
2710
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2711
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2712
- ): Snapshot {
2713
- currentHookNameInDev = 'useMutableSource';
2714
- updateHookTypesDev();
2715
- return mountMutableSource(source, getSnapshot, subscribe);
2716
- },
2366
useSyncExternalStore<T>(
2367
subscribe: (() => void) => () => void,
2368
getSnapshot: () => T,
@@ -2845,15 +2494,6 @@ if (__DEV__) {
2494
updateHookTypesDev();
2495
return updateTransition();
2496
},
2848
- useMutableSource<Source, Snapshot>(
2849
- source: MutableSource<Source>,
2850
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2851
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2852
- ): Snapshot {
2853
- currentHookNameInDev = 'useMutableSource';
2854
- updateHookTypesDev();
2855
- return updateMutableSource(source, getSnapshot, subscribe);
2856
- },
2497
useSyncExternalStore<T>(
2498
subscribe: (() => void) => () => void,
2499
getSnapshot: () => T,
@@ -2986,15 +2626,6 @@ if (__DEV__) {
2626
updateHookTypesDev();
2627
return rerenderTransition();
2628
},
2989
- useMutableSource<Source, Snapshot>(
2990
- source: MutableSource<Source>,
2991
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
2992
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
2993
- ): Snapshot {
2994
- currentHookNameInDev = 'useMutableSource';
2995
- updateHookTypesDev();
2996
- return updateMutableSource(source, getSnapshot, subscribe);
2997
- },
2629
useSyncExternalStore<T>(
2630
subscribe: (() => void) => () => void,
2631
getSnapshot: () => T,
@@ -3140,16 +2771,6 @@ if (__DEV__) {
2771
mountHookTypesDev();
2772
return mountTransition();
2773
},
3143
- useMutableSource<Source, Snapshot>(
3144
- source: MutableSource<Source>,
3145
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3146
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3147
- ): Snapshot {
3148
- currentHookNameInDev = 'useMutableSource';
3149
- warnInvalidHookAccess();
3150
- mountHookTypesDev();
3151
- return mountMutableSource(source, getSnapshot, subscribe);
3152
- },
2774
useSyncExternalStore<T>(
2775
subscribe: (() => void) => () => void,
2776
getSnapshot: () => T,
@@ -3297,16 +2918,6 @@ if (__DEV__) {
2918
updateHookTypesDev();
2919
return updateTransition();
2920
},
3300
- useMutableSource<Source, Snapshot>(
3301
- source: MutableSource<Source>,
3302
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3303
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3304
- ): Snapshot {
3305
- currentHookNameInDev = 'useMutableSource';
3306
- warnInvalidHookAccess();
3307
- updateHookTypesDev();
3308
- return updateMutableSource(source, getSnapshot, subscribe);
3309
- },
2921
useSyncExternalStore<T>(
2922
subscribe: (() => void) => () => void,
2923
getSnapshot: () => T,
@@ -3455,16 +3066,6 @@ if (__DEV__) {
3066
updateHookTypesDev();
3067
return rerenderTransition();
3068
},
3458
- useMutableSource<Source, Snapshot>(
3459
- source: MutableSource<Source>,
3460
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3461
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3462
- ): Snapshot {
3463
- currentHookNameInDev = 'useMutableSource';
3464
- warnInvalidHookAccess();
3465
- updateHookTypesDev();
3466
- return updateMutableSource(source, getSnapshot, subscribe);
3467
- },
3069
useSyncExternalStore<T>(
3070
subscribe: (() => void) => () => void,
3071
getSnapshot: () => T,
packages/react-reconciler/src/ReactFiberLane.new.js
+3
-8
@@ -283,9 +283,9 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
283
// time it takes to show the final state, which is what they are actually
284
// waiting for.
285
//
286
- // For those exceptions where entanglement is semantically important, like
287
- // useMutableSource, we should ensure that there is no partial work at the
288
- // time we apply the entanglement.
286
+ // For those exceptions where entanglement is semantically important, we
287
+ // should ensure that there is no partial work at the time we apply the
288
+ // entanglement.
289
const entangledLanes = root.entangledLanes;
290
if (entangledLanes !== NoLanes) {
291
const entanglements = root.entanglements;
@@ -617,10 +617,6 @@ export function markRootPinged(
617
root.pingedLanes |= root.suspendedLanes & pingedLanes;
618
}
619
620
-export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
621
- root.mutableReadLanes |= updateLane & root.pendingLanes;
622
-}
623
-
620
export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
621
const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
622
@@ -631,7 +627,6 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
627
root.pingedLanes = 0;
628
629
root.expiredLanes &= remainingLanes;
634
- root.mutableReadLanes &= remainingLanes;
630
631
root.entangledLanes &= remainingLanes;
632
packages/react-reconciler/src/ReactFiberLane.old.js
+3
-8
@@ -283,9 +283,9 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
283
// time it takes to show the final state, which is what they are actually
284
// waiting for.
285
//
286
- // For those exceptions where entanglement is semantically important, like
287
- // useMutableSource, we should ensure that there is no partial work at the
288
- // time we apply the entanglement.
286
+ // For those exceptions where entanglement is semantically important, we
287
+ // should ensure that there is no partial work at the time we apply the
288
+ // entanglement.
289
const entangledLanes = root.entangledLanes;
290
if (entangledLanes !== NoLanes) {
291
const entanglements = root.entanglements;
@@ -617,10 +617,6 @@ export function markRootPinged(
617
root.pingedLanes |= root.suspendedLanes & pingedLanes;
618
}
619
620
-export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
621
- root.mutableReadLanes |= updateLane & root.pendingLanes;
622
-}
623
-
620
export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
621
const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
622
@@ -631,7 +627,6 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
627
root.pingedLanes = 0;
628
629
root.expiredLanes &= remainingLanes;
634
- root.mutableReadLanes &= remainingLanes;
630
631
root.entangledLanes &= remainingLanes;
632
packages/react-reconciler/src/ReactFiberReconciler.js
-5
@@ -46,7 +46,6 @@ import {
46
findBoundingRects as findBoundingRects_old,
47
focusWithin as focusWithin_old,
48
observeVisibleRects as observeVisibleRects_old,
49
- registerMutableSourceForHydration as registerMutableSourceForHydration_old,
49
runWithPriority as runWithPriority_old,
50
getCurrentUpdatePriority as getCurrentUpdatePriority_old,
51
getIsStrictModeForDevtools as getIsStrictModeForDevtools_old,
@@ -85,7 +84,6 @@ import {
84
findBoundingRects as findBoundingRects_new,
85
focusWithin as focusWithin_new,
86
observeVisibleRects as observeVisibleRects_new,
88
- registerMutableSourceForHydration as registerMutableSourceForHydration_new,
87
runWithPriority as runWithPriority_new,
88
getCurrentUpdatePriority as getCurrentUpdatePriority_new,
89
getIsStrictModeForDevtools as getIsStrictModeForDevtools_new,
@@ -188,9 +186,6 @@ export const focusWithin = enableNewReconciler
186
export const observeVisibleRects = enableNewReconciler
187
? observeVisibleRects_new
188
: observeVisibleRects_old;
191
-export const registerMutableSourceForHydration = enableNewReconciler
192
- ? registerMutableSourceForHydration_new
193
- : registerMutableSourceForHydration_old;
189
export const runWithPriority = enableNewReconciler
190
? runWithPriority_new
191
: runWithPriority_old;
packages/react-reconciler/src/ReactFiberReconciler.new.js
-1
@@ -93,7 +93,6 @@ import {
93
} from './ReactFiberHotReloading.new';
94
import {markRenderScheduled} from './SchedulingProfiler';
95
import ReactVersion from 'shared/ReactVersion';
96
-export {registerMutableSourceForHydration} from './ReactMutableSource.new';
96
export {createPortal} from './ReactPortal';
97
export {
98
createComponentSelector,
packages/react-reconciler/src/ReactFiberReconciler.old.js
-1
@@ -93,7 +93,6 @@ import {
93
} from './ReactFiberHotReloading.old';
94
import {markRenderScheduled} from './SchedulingProfiler';
95
import ReactVersion from 'shared/ReactVersion';
96
-export {registerMutableSourceForHydration} from './ReactMutableSource.old';
96
export {createPortal} from './ReactPortal';
97
export {
98
createComponentSelector,
packages/react-reconciler/src/ReactFiberRoot.new.js
-1
@@ -49,7 +49,6 @@ function FiberRootNode(containerInfo, tag, hydrate) {
49
this.suspendedLanes = NoLanes;
50
this.pingedLanes = NoLanes;
51
this.expiredLanes = NoLanes;
52
- this.mutableReadLanes = NoLanes;
52
this.finishedLanes = NoLanes;
53
54
this.entangledLanes = NoLanes;
packages/react-reconciler/src/ReactFiberRoot.old.js
-1
@@ -49,7 +49,6 @@ function FiberRootNode(containerInfo, tag, hydrate) {
49
this.suspendedLanes = NoLanes;
50
this.pingedLanes = NoLanes;
51
this.expiredLanes = NoLanes;
52
- this.mutableReadLanes = NoLanes;
52
this.finishedLanes = NoLanes;
53
54
this.entangledLanes = NoLanes;
packages/react-reconciler/src/ReactFiberUnwindWork.new.js
-3
@@ -13,7 +13,6 @@ import type {Lanes} from './ReactFiberLane.new';
13
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
14
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.new';
15
16
-import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
16
import {
17
ClassComponent,
18
HostRoot,
@@ -83,7 +82,6 @@ function unwindWork(workInProgress: Fiber, renderLanes: Lanes) {
82
}
83
popHostContainer(workInProgress);
84
popTopLevelLegacyContextObject(workInProgress);
86
- resetMutableSourceWorkInProgressVersions();
85
const flags = workInProgress.flags;
86
invariant(
87
(flags & DidCapture) === NoFlags,
@@ -179,7 +177,6 @@ function unwindInterruptedWork(interruptedWork: Fiber, renderLanes: Lanes) {
177
}
178
popHostContainer(interruptedWork);
179
popTopLevelLegacyContextObject(interruptedWork);
182
- resetMutableSourceWorkInProgressVersions();
180
break;
181
}
182
case HostComponent: {
packages/react-reconciler/src/ReactFiberUnwindWork.old.js
-3
@@ -13,7 +13,6 @@ import type {Lanes} from './ReactFiberLane.old';
13
import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
14
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old';
15
16
-import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.old';
16
import {
17
ClassComponent,
18
HostRoot,
@@ -83,7 +82,6 @@ function unwindWork(workInProgress: Fiber, renderLanes: Lanes) {
82
}
83
popHostContainer(workInProgress);
84
popTopLevelLegacyContextObject(workInProgress);
86
- resetMutableSourceWorkInProgressVersions();
85
const flags = workInProgress.flags;
86
invariant(
87
(flags & DidCapture) === NoFlags,
@@ -179,7 +177,6 @@ function unwindInterruptedWork(interruptedWork: Fiber, renderLanes: Lanes) {
177
}
178
popHostContainer(interruptedWork);
179
popTopLevelLegacyContextObject(interruptedWork);
182
- resetMutableSourceWorkInProgressVersions();
180
break;
181
}
182
case HostComponent: {
packages/react-reconciler/src/ReactInternalTypes.js
+1
-20
@@ -8,14 +8,7 @@
8
*/
9
10
import type {Source} from 'shared/ReactElementType';
11
-import type {
12
- RefObject,
13
- ReactContext,
14
- MutableSourceSubscribeFn,
15
- MutableSourceGetSnapshotFn,
16
- MutableSourceVersion,
17
- MutableSource,
18
-} from 'shared/ReactTypes';
11
+import type {RefObject, ReactContext} from 'shared/ReactTypes';
12
import type {SuspenseInstance} from './ReactFiberHostConfig';
13
import type {WorkTag} from './ReactWorkTags';
14
import type {TypeOfMode} from './ReactTypeOfMode';
@@ -41,7 +34,6 @@ export type HookType =
34
| 'useDebugValue'
35
| 'useDeferredValue'
36
| 'useTransition'
44
- | 'useMutableSource'
37
| 'useSyncExternalStore'
38
| 'useOpaqueIdentifier'
39
| 'useCacheRefresh';
@@ -214,11 +206,6 @@ type BaseFiberRootProperties = {|
206
// Determines if we should attempt to hydrate on the initial mount
207
+hydrate: boolean,
208
217
- // Used by useMutableSource hook to avoid tearing during hydration.
218
- mutableSourceEagerHydrationData?: Array<
219
- MutableSource<any> | MutableSourceVersion,
220
- > | null,
221
-
209
// Node returned by Scheduler.scheduleCallback. Represents the next rendering
210
// task that the root will work on.
211
callbackNode: *,
@@ -230,7 +217,6 @@ type BaseFiberRootProperties = {|
217
suspendedLanes: Lanes,
218
pingedLanes: Lanes,
219
expiredLanes: Lanes,
233
- mutableReadLanes: Lanes,
220
221
finishedLanes: Lanes,
222
@@ -305,11 +291,6 @@ export type Dispatcher = {|
291
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void,
292
useDeferredValue<T>(value: T): T,
293
useTransition(): [boolean, (() => void) => void],
308
- useMutableSource<Source, Snapshot>(
309
- source: MutableSource<Source>,
310
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
311
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
312
- ): Snapshot,
294
useSyncExternalStore<T>(
295
subscribe: (() => void) => () => void,
296
getSnapshot: () => T,
packages/react-reconciler/src/ReactMutableSource.new.js
deleted
-108
@@ -1,108 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @flow
8
- */
9
-
10
-import type {MutableSource, MutableSourceVersion} from 'shared/ReactTypes';
11
-import type {FiberRoot} from './ReactInternalTypes';
12
-
13
-import {isPrimaryRenderer} from './ReactFiberHostConfig';
14
-
15
-// Work in progress version numbers only apply to a single render,
16
-// and should be reset before starting a new render.
17
-// This tracks which mutable sources need to be reset after a render.
18
-const workInProgressSources: Array<MutableSource<any>> = [];
19
-
20
-let rendererSigil;
21
-if (__DEV__) {
22
- // Used to detect multiple renderers using the same mutable source.
23
- rendererSigil = {};
24
-}
25
-
26
-export function markSourceAsDirty(mutableSource: MutableSource<any>): void {
27
- workInProgressSources.push(mutableSource);
28
-}
29
-
30
-export function resetWorkInProgressVersions(): void {
31
- for (let i = 0; i < workInProgressSources.length; i++) {
32
- const mutableSource = workInProgressSources[i];
33
- if (isPrimaryRenderer) {
34
- mutableSource._workInProgressVersionPrimary = null;
35
- } else {
36
- mutableSource._workInProgressVersionSecondary = null;
37
- }
38
- }
39
- workInProgressSources.length = 0;
40
-}
41
-
42
-export function getWorkInProgressVersion(
43
- mutableSource: MutableSource<any>,
44
-): null | MutableSourceVersion {
45
- if (isPrimaryRenderer) {
46
- return mutableSource._workInProgressVersionPrimary;
47
- } else {
48
- return mutableSource._workInProgressVersionSecondary;
49
- }
50
-}
51
-
52
-export function setWorkInProgressVersion(
53
- mutableSource: MutableSource<any>,
54
- version: MutableSourceVersion,
55
-): void {
56
- if (isPrimaryRenderer) {
57
- mutableSource._workInProgressVersionPrimary = version;
58
- } else {
59
- mutableSource._workInProgressVersionSecondary = version;
60
- }
61
- workInProgressSources.push(mutableSource);
62
-}
63
-
64
-export function warnAboutMultipleRenderersDEV(
65
- mutableSource: MutableSource<any>,
66
-): void {
67
- if (__DEV__) {
68
- if (isPrimaryRenderer) {
69
- if (mutableSource._currentPrimaryRenderer == null) {
70
- mutableSource._currentPrimaryRenderer = rendererSigil;
71
- } else if (mutableSource._currentPrimaryRenderer !== rendererSigil) {
72
- console.error(
73
- 'Detected multiple renderers concurrently rendering the ' +
74
- 'same mutable source. This is currently unsupported.',
75
- );
76
- }
77
- } else {
78
- if (mutableSource._currentSecondaryRenderer == null) {
79
- mutableSource._currentSecondaryRenderer = rendererSigil;
80
- } else if (mutableSource._currentSecondaryRenderer !== rendererSigil) {
81
- console.error(
82
- 'Detected multiple renderers concurrently rendering the ' +
83
- 'same mutable source. This is currently unsupported.',
84
- );
85
- }
86
- }
87
- }
88
-}
89
-
90
-// Eager reads the version of a mutable source and stores it on the root.
91
-// This ensures that the version used for server rendering matches the one
92
-// that is eventually read during hydration.
93
-// If they don't match there's a potential tear and a full deopt render is required.
94
-export function registerMutableSourceForHydration(
95
- root: FiberRoot,
96
- mutableSource: MutableSource<any>,
97
-): void {
98
- const getVersion = mutableSource._getVersion;
99
- const version = getVersion(mutableSource._source);
100
-
101
- // TODO Clear this data once all pending hydration work is finished.
102
- // Retaining it forever may interfere with GC.
103
- if (root.mutableSourceEagerHydrationData == null) {
104
- root.mutableSourceEagerHydrationData = [mutableSource, version];
105
- } else {
106
- root.mutableSourceEagerHydrationData.push(mutableSource, version);
107
- }
108
-}
packages/react-reconciler/src/ReactMutableSource.old.js
deleted
-108
@@ -1,108 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @flow
8
- */
9
-
10
-import type {MutableSource, MutableSourceVersion} from 'shared/ReactTypes';
11
-import type {FiberRoot} from './ReactInternalTypes';
12
-
13
-import {isPrimaryRenderer} from './ReactFiberHostConfig';
14
-
15
-// Work in progress version numbers only apply to a single render,
16
-// and should be reset before starting a new render.
17
-// This tracks which mutable sources need to be reset after a render.
18
-const workInProgressSources: Array<MutableSource<any>> = [];
19
-
20
-let rendererSigil;
21
-if (__DEV__) {
22
- // Used to detect multiple renderers using the same mutable source.
23
- rendererSigil = {};
24
-}
25
-
26
-export function markSourceAsDirty(mutableSource: MutableSource<any>): void {
27
- workInProgressSources.push(mutableSource);
28
-}
29
-
30
-export function resetWorkInProgressVersions(): void {
31
- for (let i = 0; i < workInProgressSources.length; i++) {
32
- const mutableSource = workInProgressSources[i];
33
- if (isPrimaryRenderer) {
34
- mutableSource._workInProgressVersionPrimary = null;
35
- } else {
36
- mutableSource._workInProgressVersionSecondary = null;
37
- }
38
- }
39
- workInProgressSources.length = 0;
40
-}
41
-
42
-export function getWorkInProgressVersion(
43
- mutableSource: MutableSource<any>,
44
-): null | MutableSourceVersion {
45
- if (isPrimaryRenderer) {
46
- return mutableSource._workInProgressVersionPrimary;
47
- } else {
48
- return mutableSource._workInProgressVersionSecondary;
49
- }
50
-}
51
-
52
-export function setWorkInProgressVersion(
53
- mutableSource: MutableSource<any>,
54
- version: MutableSourceVersion,
55
-): void {
56
- if (isPrimaryRenderer) {
57
- mutableSource._workInProgressVersionPrimary = version;
58
- } else {
59
- mutableSource._workInProgressVersionSecondary = version;
60
- }
61
- workInProgressSources.push(mutableSource);
62
-}
63
-
64
-export function warnAboutMultipleRenderersDEV(
65
- mutableSource: MutableSource<any>,
66
-): void {
67
- if (__DEV__) {
68
- if (isPrimaryRenderer) {
69
- if (mutableSource._currentPrimaryRenderer == null) {
70
- mutableSource._currentPrimaryRenderer = rendererSigil;
71
- } else if (mutableSource._currentPrimaryRenderer !== rendererSigil) {
72
- console.error(
73
- 'Detected multiple renderers concurrently rendering the ' +
74
- 'same mutable source. This is currently unsupported.',
75
- );
76
- }
77
- } else {
78
- if (mutableSource._currentSecondaryRenderer == null) {
79
- mutableSource._currentSecondaryRenderer = rendererSigil;
80
- } else if (mutableSource._currentSecondaryRenderer !== rendererSigil) {
81
- console.error(
82
- 'Detected multiple renderers concurrently rendering the ' +
83
- 'same mutable source. This is currently unsupported.',
84
- );
85
- }
86
- }
87
- }
88
-}
89
-
90
-// Eager reads the version of a mutable source and stores it on the root.
91
-// This ensures that the version used for server rendering matches the one
92
-// that is eventually read during hydration.
93
-// If they don't match there's a potential tear and a full deopt render is required.
94
-export function registerMutableSourceForHydration(
95
- root: FiberRoot,
96
- mutableSource: MutableSource<any>,
97
-): void {
98
- const getVersion = mutableSource._getVersion;
99
- const version = getVersion(mutableSource._source);
100
-
101
- // TODO Clear this data once all pending hydration work is finished.
102
- // Retaining it forever may interfere with GC.
103
- if (root.mutableSourceEagerHydrationData == null) {
104
- root.mutableSourceEagerHydrationData = [mutableSource, version];
105
- } else {
106
- root.mutableSourceEagerHydrationData.push(mutableSource, version);
107
- }
108
-}
packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js
deleted
-2098
@@ -1,2098 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @emails react-core
8
- * @jest-environment node
9
- */
10
-
11
-/* eslint-disable no-func-assign */
12
-
13
-'use strict';
14
-
15
-let React;
16
-let ReactFeatureFlags;
17
-let ReactNoop;
18
-let Scheduler;
19
-let act;
20
-let createMutableSource;
21
-let useMutableSource;
22
-
23
-function loadModules() {
24
- jest.resetModules();
25
- jest.useFakeTimers();
26
-
27
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
28
- ReactFeatureFlags.enableProfilerTimer = true;
29
-
30
- React = require('react');
31
- ReactNoop = require('react-noop-renderer');
32
- Scheduler = require('scheduler');
33
- act = require('jest-react').act;
34
-
35
- // Stable entrypoints export with "unstable_" prefix.
36
- createMutableSource =
37
- React.createMutableSource || React.unstable_createMutableSource;
38
- useMutableSource = React.useMutableSource || React.unstable_useMutableSource;
39
-}
40
-
41
-describe('useMutableSource', () => {
42
- const defaultGetSnapshot = source => source.value;
43
- const defaultSubscribe = (source, callback) => source.subscribe(callback);
44
-
45
- function createComplexSource(initialValueA, initialValueB) {
46
- const callbacksA = [];
47
- const callbacksB = [];
48
- let revision = 0;
49
- let valueA = initialValueA;
50
- let valueB = initialValueB;
51
-
52
- const subscribeHelper = (callbacks, callback) => {
53
- if (callbacks.indexOf(callback) < 0) {
54
- callbacks.push(callback);
55
- }
56
- return () => {
57
- const index = callbacks.indexOf(callback);
58
- if (index >= 0) {
59
- callbacks.splice(index, 1);
60
- }
61
- };
62
- };
63
-
64
- return {
65
- subscribeA(callback) {
66
- return subscribeHelper(callbacksA, callback);
67
- },
68
- subscribeB(callback) {
69
- return subscribeHelper(callbacksB, callback);
70
- },
71
-
72
- get listenerCountA() {
73
- return callbacksA.length;
74
- },
75
- get listenerCountB() {
76
- return callbacksB.length;
77
- },
78
-
79
- set valueA(newValue) {
80
- revision++;
81
- valueA = newValue;
82
- callbacksA.forEach(callback => callback());
83
- },
84
- get valueA() {
85
- return valueA;
86
- },
87
-
88
- set valueB(newValue) {
89
- revision++;
90
- valueB = newValue;
91
- callbacksB.forEach(callback => callback());
92
- },
93
- get valueB() {
94
- return valueB;
95
- },
96
-
97
- get version() {
98
- return revision;
99
- },
100
- };
101
- }
102
-
103
- function createSource(initialValue) {
104
- const callbacks = [];
105
- let revision = 0;
106
- let value = initialValue;
107
- return {
108
- subscribe(callback) {
109
- if (callbacks.indexOf(callback) < 0) {
110
- callbacks.push(callback);
111
- }
112
- return () => {
113
- const index = callbacks.indexOf(callback);
114
- if (index >= 0) {
115
- callbacks.splice(index, 1);
116
- }
117
- };
118
- },
119
- get listenerCount() {
120
- return callbacks.length;
121
- },
122
- set value(newValue) {
123
- revision++;
124
- value = newValue;
125
- callbacks.forEach(callback => callback());
126
- },
127
- get value() {
128
- return value;
129
- },
130
- get version() {
131
- return revision;
132
- },
133
- };
134
- }
135
-
136
- function Component({getSnapshot, label, mutableSource, subscribe}) {
137
- const snapshot = useMutableSource(mutableSource, getSnapshot, subscribe);
138
- Scheduler.unstable_yieldValue(`${label}:${snapshot}`);
139
- return <div>{`${label}:${snapshot}`}</div>;
140
- }
141
-
142
- beforeEach(loadModules);
143
-
144
- it('should subscribe to a source and schedule updates when it changes', () => {
145
- const source = createSource('one');
146
- const mutableSource = createMutableSource(source, param => param.version);
147
-
148
- act(() => {
149
- ReactNoop.renderToRootWithID(
150
- <>
151
- <Component
152
- label="a"
153
- getSnapshot={defaultGetSnapshot}
154
- mutableSource={mutableSource}
155
- subscribe={defaultSubscribe}
156
- />
157
- <Component
158
- label="b"
159
- getSnapshot={defaultGetSnapshot}
160
- mutableSource={mutableSource}
161
- subscribe={defaultSubscribe}
162
- />
163
- </>,
164
- 'root',
165
- () => Scheduler.unstable_yieldValue('Sync effect'),
166
- );
167
- expect(Scheduler).toFlushAndYieldThrough([
168
- 'a:one',
169
- 'b:one',
170
- 'Sync effect',
171
- ]);
172
-
173
- // Subscriptions should be passive
174
- expect(source.listenerCount).toBe(0);
175
- ReactNoop.flushPassiveEffects();
176
- expect(source.listenerCount).toBe(2);
177
-
178
- // Changing values should schedule an update with React
179
- source.value = 'two';
180
- expect(Scheduler).toFlushAndYieldThrough(['a:two', 'b:two']);
181
-
182
- // Unmounting a component should remove its subscription.
183
- ReactNoop.renderToRootWithID(
184
- <>
185
- <Component
186
- label="a"
187
- getSnapshot={defaultGetSnapshot}
188
- mutableSource={mutableSource}
189
- subscribe={defaultSubscribe}
190
- />
191
- </>,
192
- 'root',
193
- () => Scheduler.unstable_yieldValue('Sync effect'),
194
- );
195
- expect(Scheduler).toFlushAndYield(['a:two', 'Sync effect']);
196
- ReactNoop.flushPassiveEffects();
197
- expect(source.listenerCount).toBe(1);
198
-
199
- // Unmounting a root should remove the remaining event listeners
200
- ReactNoop.unmountRootWithID('root');
201
- expect(Scheduler).toFlushAndYield([]);
202
- ReactNoop.flushPassiveEffects();
203
- expect(source.listenerCount).toBe(0);
204
-
205
- // Changes to source should not trigger an updates or warnings.
206
- source.value = 'three';
207
- expect(Scheduler).toFlushAndYield([]);
208
- });
209
- });
210
-
211
- it('should restart work if a new source is mutated during render', () => {
212
- const source = createSource('one');
213
- const mutableSource = createMutableSource(source, param => param.version);
214
-
215
- act(() => {
216
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
217
- React.startTransition(() => {
218
- ReactNoop.render(
219
- <>
220
- <Component
221
- label="a"
222
- getSnapshot={defaultGetSnapshot}
223
- mutableSource={mutableSource}
224
- subscribe={defaultSubscribe}
225
- />
226
- <Component
227
- label="b"
228
- getSnapshot={defaultGetSnapshot}
229
- mutableSource={mutableSource}
230
- subscribe={defaultSubscribe}
231
- />
232
- </>,
233
- () => Scheduler.unstable_yieldValue('Sync effect'),
234
- );
235
- });
236
- } else {
237
- ReactNoop.render(
238
- <>
239
- <Component
240
- label="a"
241
- getSnapshot={defaultGetSnapshot}
242
- mutableSource={mutableSource}
243
- subscribe={defaultSubscribe}
244
- />
245
- <Component
246
- label="b"
247
- getSnapshot={defaultGetSnapshot}
248
- mutableSource={mutableSource}
249
- subscribe={defaultSubscribe}
250
- />
251
- </>,
252
- () => Scheduler.unstable_yieldValue('Sync effect'),
253
- );
254
- }
255
- // Do enough work to read from one component
256
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
257
-
258
- // Mutate source before continuing work
259
- source.value = 'two';
260
-
261
- // Render work should restart and the updated value should be used
262
- expect(Scheduler).toFlushAndYield(['a:two', 'b:two', 'Sync effect']);
263
- });
264
- });
265
-
266
- it('should schedule an update if a new source is mutated between render and commit (subscription)', () => {
267
- const source = createSource('one');
268
- const mutableSource = createMutableSource(source, param => param.version);
269
-
270
- act(() => {
271
- ReactNoop.render(
272
- <>
273
- <Component
274
- label="a"
275
- getSnapshot={defaultGetSnapshot}
276
- mutableSource={mutableSource}
277
- subscribe={defaultSubscribe}
278
- />
279
- <Component
280
- label="b"
281
- getSnapshot={defaultGetSnapshot}
282
- mutableSource={mutableSource}
283
- subscribe={defaultSubscribe}
284
- />
285
- </>,
286
- () => Scheduler.unstable_yieldValue('Sync effect'),
287
- );
288
-
289
- // Finish rendering
290
- expect(Scheduler).toFlushAndYieldThrough([
291
- 'a:one',
292
- 'b:one',
293
- 'Sync effect',
294
- ]);
295
-
296
- // Mutate source before subscriptions are attached
297
- expect(source.listenerCount).toBe(0);
298
- source.value = 'two';
299
-
300
- // Mutation should be detected, and a new render should be scheduled
301
- expect(Scheduler).toFlushAndYield(['a:two', 'b:two']);
302
- });
303
- });
304
-
305
- it('should unsubscribe and resubscribe if a new source is used', () => {
306
- const sourceA = createSource('a-one');
307
- const mutableSourceA = createMutableSource(
308
- sourceA,
309
- param => param.versionA,
310
- );
311
-
312
- const sourceB = createSource('b-one');
313
- const mutableSourceB = createMutableSource(
314
- sourceB,
315
- param => param.versionB,
316
- );
317
-
318
- act(() => {
319
- ReactNoop.render(
320
- <Component
321
- label="only"
322
- getSnapshot={defaultGetSnapshot}
323
- mutableSource={mutableSourceA}
324
- subscribe={defaultSubscribe}
325
- />,
326
- () => Scheduler.unstable_yieldValue('Sync effect'),
327
- );
328
- expect(Scheduler).toFlushAndYield(['only:a-one', 'Sync effect']);
329
- ReactNoop.flushPassiveEffects();
330
- expect(sourceA.listenerCount).toBe(1);
331
-
332
- // Changing values should schedule an update with React
333
- sourceA.value = 'a-two';
334
- expect(Scheduler).toFlushAndYield(['only:a-two']);
335
-
336
- // If we re-render with a new source, the old one should be unsubscribed.
337
- ReactNoop.render(
338
- <Component
339
- label="only"
340
- getSnapshot={defaultGetSnapshot}
341
- mutableSource={mutableSourceB}
342
- subscribe={defaultSubscribe}
343
- />,
344
- () => Scheduler.unstable_yieldValue('Sync effect'),
345
- );
346
- expect(Scheduler).toFlushAndYield(['only:b-one', 'Sync effect']);
347
- ReactNoop.flushPassiveEffects();
348
- expect(sourceA.listenerCount).toBe(0);
349
- expect(sourceB.listenerCount).toBe(1);
350
-
351
- // Changing to original source should not schedule updates with React
352
- sourceA.value = 'a-three';
353
- expect(Scheduler).toFlushAndYield([]);
354
-
355
- // Changing new source value should schedule an update with React
356
- sourceB.value = 'b-two';
357
- expect(Scheduler).toFlushAndYield(['only:b-two']);
358
- });
359
- });
360
-
361
- it('should unsubscribe and resubscribe if a new subscribe function is provided', () => {
362
- const source = createSource('a-one');
363
- const mutableSource = createMutableSource(source, param => param.version);
364
-
365
- const unsubscribeA = jest.fn();
366
- const subscribeA = jest.fn(s => {
367
- const unsubscribe = defaultSubscribe(s);
368
- return () => {
369
- unsubscribe();
370
- unsubscribeA();
371
- };
372
- });
373
- const unsubscribeB = jest.fn();
374
- const subscribeB = jest.fn(s => {
375
- const unsubscribe = defaultSubscribe(s);
376
- return () => {
377
- unsubscribe();
378
- unsubscribeB();
379
- };
380
- });
381
-
382
- act(() => {
383
- ReactNoop.renderToRootWithID(
384
- <Component
385
- label="only"
386
- getSnapshot={defaultGetSnapshot}
387
- mutableSource={mutableSource}
388
- subscribe={subscribeA}
389
- />,
390
- 'root',
391
- () => Scheduler.unstable_yieldValue('Sync effect'),
392
- );
393
- expect(Scheduler).toFlushAndYield(['only:a-one', 'Sync effect']);
394
- ReactNoop.flushPassiveEffects();
395
- expect(source.listenerCount).toBe(1);
396
- expect(subscribeA).toHaveBeenCalledTimes(1);
397
-
398
- // If we re-render with a new subscription function,
399
- // the old unsubscribe function should be called.
400
- ReactNoop.renderToRootWithID(
401
- <Component
402
- label="only"
403
- getSnapshot={defaultGetSnapshot}
404
- mutableSource={mutableSource}
405
- subscribe={subscribeB}
406
- />,
407
- 'root',
408
- () => Scheduler.unstable_yieldValue('Sync effect'),
409
- );
410
- expect(Scheduler).toFlushAndYield(['only:a-one', 'Sync effect']);
411
- ReactNoop.flushPassiveEffects();
412
- expect(source.listenerCount).toBe(1);
413
- expect(unsubscribeA).toHaveBeenCalledTimes(1);
414
- expect(subscribeB).toHaveBeenCalledTimes(1);
415
-
416
- // Unmounting should call the newer unsubscribe.
417
- ReactNoop.unmountRootWithID('root');
418
- expect(Scheduler).toFlushAndYield([]);
419
- ReactNoop.flushPassiveEffects();
420
- expect(source.listenerCount).toBe(0);
421
- expect(unsubscribeB).toHaveBeenCalledTimes(1);
422
- });
423
- });
424
-
425
- it('should re-use previously read snapshot value when reading is unsafe', () => {
426
- const source = createSource('one');
427
- const mutableSource = createMutableSource(source, param => param.version);
428
-
429
- act(() => {
430
- ReactNoop.render(
431
- <>
432
- <Component
433
- label="a"
434
- getSnapshot={defaultGetSnapshot}
435
- mutableSource={mutableSource}
436
- subscribe={defaultSubscribe}
437
- />
438
- <Component
439
- label="b"
440
- getSnapshot={defaultGetSnapshot}
441
- mutableSource={mutableSource}
442
- subscribe={defaultSubscribe}
443
- />
444
- </>,
445
- () => Scheduler.unstable_yieldValue('Sync effect'),
446
- );
447
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
448
-
449
- // Changing values should schedule an update with React.
450
- // Start working on this update but don't finish it.
451
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
452
- React.startTransition(() => {
453
- source.value = 'two';
454
- });
455
- } else {
456
- source.value = 'two';
457
- }
458
- expect(Scheduler).toFlushAndYieldThrough(['a:two']);
459
-
460
- // Re-renders that occur before the update is processed
461
- // should reuse snapshot so long as the config has not changed
462
- ReactNoop.flushSync(() => {
463
- ReactNoop.render(
464
- <>
465
- <Component
466
- label="a"
467
- getSnapshot={defaultGetSnapshot}
468
- mutableSource={mutableSource}
469
- subscribe={defaultSubscribe}
470
- />
471
- <Component
472
- label="b"
473
- getSnapshot={defaultGetSnapshot}
474
- mutableSource={mutableSource}
475
- subscribe={defaultSubscribe}
476
- />
477
- </>,
478
- () => Scheduler.unstable_yieldValue('Sync effect'),
479
- );
480
- });
481
- expect(Scheduler).toHaveYielded(['a:one', 'b:one', 'Sync effect']);
482
-
483
- expect(Scheduler).toFlushAndYield(['a:two', 'b:two']);
484
- });
485
- });
486
-
487
- it('should read from source on newly mounted subtree if no pending updates are scheduled for source', () => {
488
- const source = createSource('one');
489
- const mutableSource = createMutableSource(source, param => param.version);
490
-
491
- act(() => {
492
- ReactNoop.render(
493
- <>
494
- <Component
495
- label="a"
496
- getSnapshot={defaultGetSnapshot}
497
- mutableSource={mutableSource}
498
- subscribe={defaultSubscribe}
499
- />
500
- </>,
501
- () => Scheduler.unstable_yieldValue('Sync effect'),
502
- );
503
- expect(Scheduler).toFlushAndYield(['a:one', 'Sync effect']);
504
-
505
- ReactNoop.render(
506
- <>
507
- <Component
508
- label="a"
509
- getSnapshot={defaultGetSnapshot}
510
- mutableSource={mutableSource}
511
- subscribe={defaultSubscribe}
512
- />
513
- <Component
514
- label="b"
515
- getSnapshot={defaultGetSnapshot}
516
- mutableSource={mutableSource}
517
- subscribe={defaultSubscribe}
518
- />
519
- </>,
520
- () => Scheduler.unstable_yieldValue('Sync effect'),
521
- );
522
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
523
- });
524
- });
525
-
526
- it('should throw and restart render if source and snapshot are unavailable during an update', () => {
527
- const source = createSource('one');
528
- const mutableSource = createMutableSource(source, param => param.version);
529
-
530
- act(() => {
531
- ReactNoop.render(
532
- <>
533
- <Component
534
- label="a"
535
- getSnapshot={defaultGetSnapshot}
536
- mutableSource={mutableSource}
537
- subscribe={defaultSubscribe}
538
- />
539
- <Component
540
- label="b"
541
- getSnapshot={defaultGetSnapshot}
542
- mutableSource={mutableSource}
543
- subscribe={defaultSubscribe}
544
- />
545
- </>,
546
- () => Scheduler.unstable_yieldValue('Sync effect'),
547
- );
548
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
549
- ReactNoop.flushPassiveEffects();
550
-
551
- // Changing values should schedule an update with React.
552
- // Start working on this update but don't finish it.
553
- ReactNoop.idleUpdates(() => {
554
- source.value = 'two';
555
- expect(Scheduler).toFlushAndYieldThrough(['a:two']);
556
- });
557
-
558
- const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
559
-
560
- // Force a higher priority render with a new config.
561
- // This should signal that the snapshot is not safe and trigger a full re-render.
562
- ReactNoop.flushSync(() => {
563
- ReactNoop.render(
564
- <>
565
- <Component
566
- label="a"
567
- getSnapshot={newGetSnapshot}
568
- mutableSource={mutableSource}
569
- subscribe={defaultSubscribe}
570
- />
571
- <Component
572
- label="b"
573
- getSnapshot={newGetSnapshot}
574
- mutableSource={mutableSource}
575
- subscribe={defaultSubscribe}
576
- />
577
- </>,
578
- () => Scheduler.unstable_yieldValue('Sync effect'),
579
- );
580
- });
581
- expect(Scheduler).toHaveYielded([
582
- 'a:new:two',
583
- 'b:new:two',
584
- 'Sync effect',
585
- ]);
586
- });
587
- });
588
-
589
- it('should throw and restart render if source and snapshot are unavailable during a sync update', () => {
590
- const source = createSource('one');
591
- const mutableSource = createMutableSource(source, param => param.version);
592
-
593
- act(() => {
594
- ReactNoop.render(
595
- <>
596
- <Component
597
- label="a"
598
- getSnapshot={defaultGetSnapshot}
599
- mutableSource={mutableSource}
600
- subscribe={defaultSubscribe}
601
- />
602
- <Component
603
- label="b"
604
- getSnapshot={defaultGetSnapshot}
605
- mutableSource={mutableSource}
606
- subscribe={defaultSubscribe}
607
- />
608
- </>,
609
- () => Scheduler.unstable_yieldValue('Sync effect'),
610
- );
611
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
612
- ReactNoop.flushPassiveEffects();
613
-
614
- // Changing values should schedule an update with React.
615
- // Start working on this update but don't finish it.
616
- ReactNoop.idleUpdates(() => {
617
- source.value = 'two';
618
- expect(Scheduler).toFlushAndYieldThrough(['a:two']);
619
- });
620
-
621
- const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
622
-
623
- // Force a higher priority render with a new config.
624
- // This should signal that the snapshot is not safe and trigger a full re-render.
625
- ReactNoop.flushSync(() => {
626
- ReactNoop.render(
627
- <>
628
- <Component
629
- label="a"
630
- getSnapshot={newGetSnapshot}
631
- mutableSource={mutableSource}
632
- subscribe={defaultSubscribe}
633
- />
634
- <Component
635
- label="b"
636
- getSnapshot={newGetSnapshot}
637
- mutableSource={mutableSource}
638
- subscribe={defaultSubscribe}
639
- />
640
- </>,
641
- () => Scheduler.unstable_yieldValue('Sync effect'),
642
- );
643
- });
644
- expect(Scheduler).toHaveYielded([
645
- 'a:new:two',
646
- 'b:new:two',
647
- 'Sync effect',
648
- ]);
649
- });
650
- });
651
-
652
- it('should only update components whose subscriptions fire', () => {
653
- const source = createComplexSource('a:one', 'b:one');
654
- const mutableSource = createMutableSource(source, param => param.version);
655
-
656
- // Subscribe to part of the store.
657
- const getSnapshotA = s => s.valueA;
658
- const subscribeA = (s, callback) => s.subscribeA(callback);
659
- const getSnapshotB = s => s.valueB;
660
- const subscribeB = (s, callback) => s.subscribeB(callback);
661
-
662
- act(() => {
663
- ReactNoop.render(
664
- <>
665
- <Component
666
- label="a"
667
- getSnapshot={getSnapshotA}
668
- mutableSource={mutableSource}
669
- subscribe={subscribeA}
670
- />
671
- <Component
672
- label="b"
673
- getSnapshot={getSnapshotB}
674
- mutableSource={mutableSource}
675
- subscribe={subscribeB}
676
- />
677
- </>,
678
- () => Scheduler.unstable_yieldValue('Sync effect'),
679
- );
680
- expect(Scheduler).toFlushAndYield(['a:a:one', 'b:b:one', 'Sync effect']);
681
-
682
- // Changes to part of the store (e.g. A) should not render other parts.
683
- source.valueA = 'a:two';
684
- expect(Scheduler).toFlushAndYield(['a:a:two']);
685
- source.valueB = 'b:two';
686
- expect(Scheduler).toFlushAndYield(['b:b:two']);
687
- });
688
- });
689
-
690
- it('should detect tearing in part of the store not yet subscribed to', () => {
691
- const source = createComplexSource('a:one', 'b:one');
692
- const mutableSource = createMutableSource(source, param => param.version);
693
-
694
- // Subscribe to part of the store.
695
- const getSnapshotA = s => s.valueA;
696
- const subscribeA = (s, callback) => s.subscribeA(callback);
697
- const getSnapshotB = s => s.valueB;
698
- const subscribeB = (s, callback) => s.subscribeB(callback);
699
-
700
- act(() => {
701
- ReactNoop.render(
702
- <>
703
- <Component
704
- label="a"
705
- getSnapshot={getSnapshotA}
706
- mutableSource={mutableSource}
707
- subscribe={subscribeA}
708
- />
709
- </>,
710
- () => Scheduler.unstable_yieldValue('Sync effect'),
711
- );
712
- expect(Scheduler).toFlushAndYield(['a:a:one', 'Sync effect']);
713
-
714
- // Because the store has not changed yet, there are no pending updates,
715
- // so it is considered safe to read from when we start this render.
716
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
717
- React.startTransition(() => {
718
- ReactNoop.render(
719
- <>
720
- <Component
721
- label="a"
722
- getSnapshot={getSnapshotA}
723
- mutableSource={mutableSource}
724
- subscribe={subscribeA}
725
- />
726
- <Component
727
- label="b"
728
- getSnapshot={getSnapshotB}
729
- mutableSource={mutableSource}
730
- subscribe={subscribeB}
731
- />
732
- <Component
733
- label="c"
734
- getSnapshot={getSnapshotB}
735
- mutableSource={mutableSource}
736
- subscribe={subscribeB}
737
- />
738
- </>,
739
- () => Scheduler.unstable_yieldValue('Sync effect'),
740
- );
741
- });
742
- } else {
743
- ReactNoop.render(
744
- <>
745
- <Component
746
- label="a"
747
- getSnapshot={getSnapshotA}
748
- mutableSource={mutableSource}
749
- subscribe={subscribeA}
750
- />
751
- <Component
752
- label="b"
753
- getSnapshot={getSnapshotB}
754
- mutableSource={mutableSource}
755
- subscribe={subscribeB}
756
- />
757
- <Component
758
- label="c"
759
- getSnapshot={getSnapshotB}
760
- mutableSource={mutableSource}
761
- subscribe={subscribeB}
762
- />
763
- </>,
764
- () => Scheduler.unstable_yieldValue('Sync effect'),
765
- );
766
- }
767
- expect(Scheduler).toFlushAndYieldThrough(['a:a:one', 'b:b:one']);
768
-
769
- // Mutating the source should trigger a tear detection on the next read,
770
- // which should throw and re-render the entire tree.
771
- source.valueB = 'b:two';
772
-
773
- expect(Scheduler).toFlushAndYield([
774
- 'a:a:one',
775
- 'b:b:two',
776
- 'c:b:two',
777
- 'Sync effect',
778
- ]);
779
- });
780
- });
781
-
782
- it('does not schedule an update for subscriptions that fire with an unchanged snapshot', () => {
783
- const MockComponent = jest.fn(Component);
784
-
785
- const source = createSource('one');
786
- const mutableSource = createMutableSource(source, param => param.version);
787
-
788
- act(() => {
789
- ReactNoop.render(
790
- <MockComponent
791
- label="only"
792
- getSnapshot={defaultGetSnapshot}
793
- mutableSource={mutableSource}
794
- subscribe={defaultSubscribe}
795
- />,
796
- () => Scheduler.unstable_yieldValue('Sync effect'),
797
- );
798
- expect(Scheduler).toFlushAndYieldThrough(['only:one', 'Sync effect']);
799
- ReactNoop.flushPassiveEffects();
800
- expect(source.listenerCount).toBe(1);
801
-
802
- // Notify subscribe function but don't change the value
803
- source.value = 'one';
804
- expect(Scheduler).toFlushWithoutYielding();
805
- });
806
- });
807
-
808
- it('should throw and restart if getSnapshot changes between scheduled update and re-render', () => {
809
- const source = createSource('one');
810
- const mutableSource = createMutableSource(source, param => param.version);
811
-
812
- const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
813
-
814
- let updateGetSnapshot;
815
-
816
- function WrapperWithState() {
817
- const tuple = React.useState(() => defaultGetSnapshot);
818
- updateGetSnapshot = tuple[1];
819
- return (
820
- <Component
821
- label="only"
822
- getSnapshot={tuple[0]}
823
- mutableSource={mutableSource}
824
- subscribe={defaultSubscribe}
825
- />
826
- );
827
- }
828
-
829
- act(() => {
830
- ReactNoop.render(<WrapperWithState />, () =>
831
- Scheduler.unstable_yieldValue('Sync effect'),
832
- );
833
- expect(Scheduler).toFlushAndYield(['only:one', 'Sync effect']);
834
- ReactNoop.flushPassiveEffects();
835
-
836
- // Change the source (and schedule an update).
837
- source.value = 'two';
838
-
839
- // Schedule a higher priority update that changes getSnapshot.
840
- ReactNoop.flushSync(() => {
841
- updateGetSnapshot(() => newGetSnapshot);
842
- });
843
-
844
- expect(Scheduler).toHaveYielded(['only:new:two']);
845
- });
846
- });
847
-
848
- it('should recover from a mutation during yield when other work is scheduled', () => {
849
- const source = createSource('one');
850
- const mutableSource = createMutableSource(source, param => param.version);
851
-
852
- act(() => {
853
- // Start a render that uses the mutable source.
854
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
855
- React.startTransition(() => {
856
- ReactNoop.render(
857
- <>
858
- <Component
859
- label="a"
860
- getSnapshot={defaultGetSnapshot}
861
- mutableSource={mutableSource}
862
- subscribe={defaultSubscribe}
863
- />
864
- <Component
865
- label="b"
866
- getSnapshot={defaultGetSnapshot}
867
- mutableSource={mutableSource}
868
- subscribe={defaultSubscribe}
869
- />
870
- </>,
871
- );
872
- });
873
- } else {
874
- ReactNoop.render(
875
- <>
876
- <Component
877
- label="a"
878
- getSnapshot={defaultGetSnapshot}
879
- mutableSource={mutableSource}
880
- subscribe={defaultSubscribe}
881
- />
882
- <Component
883
- label="b"
884
- getSnapshot={defaultGetSnapshot}
885
- mutableSource={mutableSource}
886
- subscribe={defaultSubscribe}
887
- />
888
- </>,
889
- );
890
- }
891
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
892
-
893
- // Mutate source
894
- source.value = 'two';
895
-
896
- // Now render something different.
897
- ReactNoop.render(<div />);
898
- expect(Scheduler).toFlushAndYield([]);
899
- });
900
- });
901
-
902
- it('should not throw if the new getSnapshot returns the same snapshot value', () => {
903
- const source = createSource('one');
904
- const mutableSource = createMutableSource(source, param => param.version);
905
-
906
- const onRenderA = jest.fn();
907
- const onRenderB = jest.fn();
908
-
909
- let updateGetSnapshot;
910
-
911
- function WrapperWithState() {
912
- const tuple = React.useState(() => defaultGetSnapshot);
913
- updateGetSnapshot = tuple[1];
914
- return (
915
- <Component
916
- label="b"
917
- getSnapshot={tuple[0]}
918
- mutableSource={mutableSource}
919
- subscribe={defaultSubscribe}
920
- />
921
- );
922
- }
923
-
924
- act(() => {
925
- ReactNoop.render(
926
- <>
927
- <React.Profiler id="a" onRender={onRenderA}>
928
- <Component
929
- label="a"
930
- getSnapshot={defaultGetSnapshot}
931
- mutableSource={mutableSource}
932
- subscribe={defaultSubscribe}
933
- />
934
- </React.Profiler>
935
- <React.Profiler id="b" onRender={onRenderB}>
936
- <WrapperWithState />
937
- </React.Profiler>
938
- </>,
939
- () => Scheduler.unstable_yieldValue('Sync effect'),
940
- );
941
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
942
- ReactNoop.flushPassiveEffects();
943
- expect(onRenderA).toHaveBeenCalledTimes(1);
944
- expect(onRenderB).toHaveBeenCalledTimes(1);
945
-
946
- // If B's getSnapshot function updates, but the snapshot it returns is the same,
947
- // only B should re-render (to update its state).
948
- updateGetSnapshot(() => s => defaultGetSnapshot(s));
949
- expect(Scheduler).toFlushAndYield(['b:one']);
950
- ReactNoop.flushPassiveEffects();
951
- expect(onRenderA).toHaveBeenCalledTimes(1);
952
- expect(onRenderB).toHaveBeenCalledTimes(2);
953
- });
954
- });
955
-
956
- it('should not throw if getSnapshot changes but the source can be safely read from anyway', () => {
957
- const source = createSource('one');
958
- const mutableSource = createMutableSource(source, param => param.version);
959
-
960
- const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
961
-
962
- let updateGetSnapshot;
963
-
964
- function WrapperWithState() {
965
- const tuple = React.useState(() => defaultGetSnapshot);
966
- updateGetSnapshot = tuple[1];
967
- return (
968
- <Component
969
- label="only"
970
- getSnapshot={tuple[0]}
971
- mutableSource={mutableSource}
972
- subscribe={defaultSubscribe}
973
- />
974
- );
975
- }
976
-
977
- act(() => {
978
- ReactNoop.render(<WrapperWithState />, () =>
979
- Scheduler.unstable_yieldValue('Sync effect'),
980
- );
981
- expect(Scheduler).toFlushAndYield(['only:one', 'Sync effect']);
982
- ReactNoop.flushPassiveEffects();
983
-
984
- // Change the source (and schedule an update)
985
- // but also change the snapshot function too.
986
- ReactNoop.batchedUpdates(() => {
987
- source.value = 'two';
988
- updateGetSnapshot(() => newGetSnapshot);
989
- });
990
-
991
- expect(Scheduler).toFlushAndYield(['only:new:two']);
992
- });
993
- });
994
-
995
- it('should still schedule an update if an eager selector throws after a mutation', () => {
996
- const source = createSource({
997
- friends: [
998
- {id: 1, name: 'Foo'},
999
- {id: 2, name: 'Bar'},
1000
- ],
1001
- });
1002
- const mutableSource = createMutableSource(source, param => param.version);
1003
-
1004
- function FriendsList() {
1005
- const getSnapshot = React.useCallback(
1006
- ({value}) => Array.from(value.friends),
1007
- [],
1008
- );
1009
- const friends = useMutableSource(
1010
- mutableSource,
1011
- getSnapshot,
1012
- defaultSubscribe,
1013
- );
1014
- return (
1015
- <ul>
1016
- {friends.map(friend => (
1017
- <Friend key={friend.id} id={friend.id} />
1018
- ))}
1019
- </ul>
1020
- );
1021
- }
1022
-
1023
- function Friend({id}) {
1024
- const getSnapshot = React.useCallback(
1025
- ({value}) => {
1026
- // This selector is intentionally written in a way that will throw
1027
- // if no matching friend exists in the store.
1028
- return value.friends.find(friend => friend.id === id).name;
1029
- },
1030
- [id],
1031
- );
1032
- const name = useMutableSource(
1033
- mutableSource,
1034
- getSnapshot,
1035
- defaultSubscribe,
1036
- );
1037
- Scheduler.unstable_yieldValue(`${id}:${name}`);
1038
- return <li>{name}</li>;
1039
- }
1040
-
1041
- act(() => {
1042
- ReactNoop.render(<FriendsList />, () =>
1043
- Scheduler.unstable_yieldValue('Sync effect'),
1044
- );
1045
- expect(Scheduler).toFlushAndYield(['1:Foo', '2:Bar', 'Sync effect']);
1046
-
1047
- // This mutation will cause the "Bar" component to throw,
1048
- // since its value will no longer be a part of the store.
1049
- // Mutable source should still schedule an update though,
1050
- // which should unmount "Bar" and mount "Baz".
1051
- source.value = {
1052
- friends: [
1053
- {id: 1, name: 'Foo'},
1054
- {id: 3, name: 'Baz'},
1055
- ],
1056
- };
1057
- expect(Scheduler).toFlushAndYield(['1:Foo', '3:Baz']);
1058
- });
1059
- });
1060
-
1061
- it('should not warn about updates that fire between unmount and passive unsubscribe', () => {
1062
- const source = createSource('one');
1063
- const mutableSource = createMutableSource(source, param => param.version);
1064
-
1065
- function Wrapper() {
1066
- React.useLayoutEffect(() => () => {
1067
- Scheduler.unstable_yieldValue('layout unmount');
1068
- });
1069
- return (
1070
- <Component
1071
- label="only"
1072
- getSnapshot={defaultGetSnapshot}
1073
- mutableSource={mutableSource}
1074
- subscribe={defaultSubscribe}
1075
- />
1076
- );
1077
- }
1078
-
1079
- act(() => {
1080
- ReactNoop.renderToRootWithID(<Wrapper />, 'root', () =>
1081
- Scheduler.unstable_yieldValue('Sync effect'),
1082
- );
1083
- expect(Scheduler).toFlushAndYield(['only:one', 'Sync effect']);
1084
- ReactNoop.flushPassiveEffects();
1085
-
1086
- // Unmounting a root should remove the remaining event listeners in a passive effect
1087
- ReactNoop.unmountRootWithID('root');
1088
- expect(Scheduler).toFlushAndYieldThrough(['layout unmount']);
1089
-
1090
- // Changes to source should not cause a warning,
1091
- // even though the unsubscribe hasn't run yet (since it's a pending passive effect).
1092
- source.value = 'two';
1093
- expect(Scheduler).toFlushAndYield([]);
1094
- });
1095
- });
1096
-
1097
- it('should support inline selectors and updates that are processed after selector change', async () => {
1098
- const source = createSource({
1099
- a: 'initial',
1100
- b: 'initial',
1101
- });
1102
- const mutableSource = createMutableSource(source, param => param.version);
1103
-
1104
- const getSnapshotA = () => source.value.a;
1105
- const getSnapshotB = () => source.value.b;
1106
-
1107
- function mutateB(newB) {
1108
- source.value = {
1109
- ...source.value,
1110
- b: newB,
1111
- };
1112
- }
1113
-
1114
- function App({getSnapshot}) {
1115
- const state = useMutableSource(
1116
- mutableSource,
1117
- getSnapshot,
1118
- defaultSubscribe,
1119
- );
1120
- return state;
1121
- }
1122
-
1123
- const root = ReactNoop.createRoot();
1124
- await act(async () => {
1125
- root.render(<App getSnapshot={getSnapshotA} />);
1126
- });
1127
- expect(root).toMatchRenderedOutput('initial');
1128
-
1129
- await act(async () => {
1130
- mutateB('Updated B');
1131
- root.render(<App getSnapshot={getSnapshotB} />);
1132
- });
1133
- expect(root).toMatchRenderedOutput('Updated B');
1134
-
1135
- await act(async () => {
1136
- mutateB('Another update');
1137
- });
1138
- expect(root).toMatchRenderedOutput('Another update');
1139
- });
1140
-
1141
- it('should clear the update queue when getSnapshot changes with pending lower priority updates', async () => {
1142
- const source = createSource({
1143
- a: 'initial',
1144
- b: 'initial',
1145
- });
1146
- const mutableSource = createMutableSource(source, param => param.version);
1147
-
1148
- const getSnapshotA = () => source.value.a;
1149
- const getSnapshotB = () => source.value.b;
1150
-
1151
- function mutateA(newA) {
1152
- source.value = {
1153
- ...source.value,
1154
- a: newA,
1155
- };
1156
- }
1157
-
1158
- function mutateB(newB) {
1159
- source.value = {
1160
- ...source.value,
1161
- b: newB,
1162
- };
1163
- }
1164
-
1165
- function App({toggle}) {
1166
- const state = useMutableSource(
1167
- mutableSource,
1168
- toggle ? getSnapshotB : getSnapshotA,
1169
- defaultSubscribe,
1170
- );
1171
- const result = (toggle ? 'B: ' : 'A: ') + state;
1172
- return result;
1173
- }
1174
-
1175
- const root = ReactNoop.createRoot();
1176
- await act(async () => {
1177
- root.render(<App toggle={false} />);
1178
- });
1179
- expect(root).toMatchRenderedOutput('A: initial');
1180
-
1181
- await act(async () => {
1182
- ReactNoop.discreteUpdates(() => {
1183
- // Update both A and B to the same value
1184
- mutateA('Update');
1185
- mutateB('Update');
1186
- // Toggle to B in the same batch
1187
- root.render(<App toggle={true} />);
1188
- });
1189
- // Mutate A at lower priority. This should never be rendered, because
1190
- // by the time we get to the lower priority, we've already switched
1191
- // to B.
1192
- mutateA('OOPS! This mutation should be ignored');
1193
- });
1194
- expect(root).toMatchRenderedOutput('B: Update');
1195
- });
1196
-
1197
- it('should clear the update queue when source changes with pending lower priority updates', async () => {
1198
- const sourceA = createSource('initial');
1199
- const sourceB = createSource('initial');
1200
- const mutableSourceA = createMutableSource(
1201
- sourceA,
1202
- param => param.versionA,
1203
- );
1204
- const mutableSourceB = createMutableSource(
1205
- sourceB,
1206
- param => param.versionB,
1207
- );
1208
-
1209
- function App({toggle}) {
1210
- const state = useMutableSource(
1211
- toggle ? mutableSourceB : mutableSourceA,
1212
- defaultGetSnapshot,
1213
- defaultSubscribe,
1214
- );
1215
- const result = (toggle ? 'B: ' : 'A: ') + state;
1216
- return result;
1217
- }
1218
-
1219
- const root = ReactNoop.createRoot();
1220
- await act(async () => {
1221
- root.render(<App toggle={false} />);
1222
- });
1223
- expect(root).toMatchRenderedOutput('A: initial');
1224
-
1225
- await act(async () => {
1226
- ReactNoop.discreteUpdates(() => {
1227
- // Update both A and B to the same value
1228
- sourceA.value = 'Update';
1229
- sourceB.value = 'Update';
1230
- // Toggle to B in the same batch
1231
- root.render(<App toggle={true} />);
1232
- });
1233
- // Mutate A at lower priority. This should never be rendered, because
1234
- // by the time we get to the lower priority, we've already switched
1235
- // to B.
1236
- sourceA.value = 'OOPS! This mutation should be ignored';
1237
- });
1238
- expect(root).toMatchRenderedOutput('B: Update');
1239
- });
1240
-
1241
- it('should always treat reading as potentially unsafe when getSnapshot changes between renders', async () => {
1242
- const source = createSource({
1243
- a: 'foo',
1244
- b: 'bar',
1245
- });
1246
- const mutableSource = createMutableSource(source, param => param.version);
1247
-
1248
- const getSnapshotA = () => source.value.a;
1249
- const getSnapshotB = () => source.value.b;
1250
-
1251
- function mutateA(newA) {
1252
- source.value = {
1253
- ...source.value,
1254
- a: newA,
1255
- };
1256
- }
1257
-
1258
- function App({getSnapshotFirst, getSnapshotSecond}) {
1259
- const first = useMutableSource(
1260
- mutableSource,
1261
- getSnapshotFirst,
1262
- defaultSubscribe,
1263
- );
1264
- const second = useMutableSource(
1265
- mutableSource,
1266
- getSnapshotSecond,
1267
- defaultSubscribe,
1268
- );
1269
-
1270
- let result = `x: ${first}, y: ${second}`;
1271
-
1272
- if (getSnapshotFirst === getSnapshotSecond) {
1273
- // When both getSnapshot functions are equal,
1274
- // the two values must be consistent.
1275
- if (first !== second) {
1276
- result = 'Oops, tearing!';
1277
- }
1278
- }
1279
-
1280
- React.useEffect(() => {
1281
- Scheduler.unstable_yieldValue(result);
1282
- }, [result]);
1283
-
1284
- return result;
1285
- }
1286
-
1287
- const root = ReactNoop.createRoot();
1288
- await act(async () => {
1289
- root.render(
1290
- <App
1291
- getSnapshotFirst={getSnapshotA}
1292
- getSnapshotSecond={getSnapshotB}
1293
- />,
1294
- );
1295
- });
1296
- // x and y start out reading from different parts of the store.
1297
- expect(Scheduler).toHaveYielded(['x: foo, y: bar']);
1298
-
1299
- await act(async () => {
1300
- ReactNoop.discreteUpdates(() => {
1301
- // At high priority, toggle y so that it reads from A instead of B.
1302
- // Simultaneously, mutate A.
1303
- mutateA('baz');
1304
- root.render(
1305
- <App
1306
- getSnapshotFirst={getSnapshotA}
1307
- getSnapshotSecond={getSnapshotA}
1308
- />,
1309
- );
1310
-
1311
- // If this update were processed before the next mutation,
1312
- // it would be expected to yield "baz" and "baz".
1313
- });
1314
-
1315
- // At lower priority, mutate A again.
1316
- // This happens to match the initial value of B.
1317
- mutateA('bar');
1318
-
1319
- // When this update is processed,
1320
- // it is expected to yield "bar" and "bar".
1321
- });
1322
-
1323
- // Check that we didn't commit any inconsistent states.
1324
- // The actual sequence of work will be:
1325
- // 1. React renders the high-pri update, sees a new getSnapshot, detects the source has been further mutated, and throws
1326
- // 2. React re-renders with all pending updates, including the second mutation, and renders "bar" and "bar".
1327
- expect(Scheduler).toHaveYielded(['x: bar, y: bar']);
1328
- });
1329
-
1330
- it('getSnapshot changes and then source is mutated in between paint and passive effect phase', async () => {
1331
- const source = createSource({
1332
- a: 'foo',
1333
- b: 'bar',
1334
- });
1335
- const mutableSource = createMutableSource(source, param => param.version);
1336
-
1337
- function mutateB(newB) {
1338
- source.value = {
1339
- ...source.value,
1340
- b: newB,
1341
- };
1342
- }
1343
-
1344
- const getSnapshotA = () => source.value.a;
1345
- const getSnapshotB = () => source.value.b;
1346
-
1347
- function App({getSnapshot}) {
1348
- const value = useMutableSource(
1349
- mutableSource,
1350
- getSnapshot,
1351
- defaultSubscribe,
1352
- );
1353
-
1354
- Scheduler.unstable_yieldValue('Render: ' + value);
1355
- React.useEffect(() => {
1356
- Scheduler.unstable_yieldValue('Commit: ' + value);
1357
- }, [value]);
1358
-
1359
- return value;
1360
- }
1361
-
1362
- const root = ReactNoop.createRoot();
1363
- await act(async () => {
1364
- root.render(<App getSnapshot={getSnapshotA} />);
1365
- });
1366
- expect(Scheduler).toHaveYielded(['Render: foo', 'Commit: foo']);
1367
-
1368
- await act(async () => {
1369
- // Switch getSnapshot to read from B instead
1370
- root.render(<App getSnapshot={getSnapshotB} />);
1371
- // Render and finish the tree, but yield right after paint, before
1372
- // the passive effects have fired.
1373
- expect(Scheduler).toFlushUntilNextPaint(['Render: bar']);
1374
- // Then mutate B.
1375
- mutateB('baz');
1376
- });
1377
- expect(Scheduler).toHaveYielded([
1378
- // Fires the effect from the previous render
1379
- 'Commit: bar',
1380
- // During that effect, it should detect that the snapshot has changed
1381
- // and re-render.
1382
- 'Render: baz',
1383
- 'Commit: baz',
1384
- ]);
1385
- expect(root).toMatchRenderedOutput('baz');
1386
- });
1387
-
1388
- it('getSnapshot changes and then source is mutated in between paint and passive effect phase, case 2', async () => {
1389
- const source = createSource({
1390
- a: 'a0',
1391
- b: 'b0',
1392
- });
1393
- const mutableSource = createMutableSource(source, param => param.version);
1394
-
1395
- const getSnapshotA = () => source.value.a;
1396
- const getSnapshotB = () => source.value.b;
1397
-
1398
- function mutateA(newA) {
1399
- source.value = {
1400
- ...source.value,
1401
- a: newA,
1402
- };
1403
- }
1404
-
1405
- function App({getSnapshotFirst, getSnapshotSecond}) {
1406
- const first = useMutableSource(
1407
- mutableSource,
1408
- getSnapshotFirst,
1409
- defaultSubscribe,
1410
- );
1411
- const second = useMutableSource(
1412
- mutableSource,
1413
- getSnapshotSecond,
1414
- defaultSubscribe,
1415
- );
1416
-
1417
- return `first: ${first}, second: ${second}`;
1418
- }
1419
-
1420
- const root = ReactNoop.createRoot();
1421
- await act(async () => {
1422
- root.render(
1423
- <App
1424
- getSnapshotFirst={getSnapshotA}
1425
- getSnapshotSecond={getSnapshotB}
1426
- />,
1427
- );
1428
- });
1429
- expect(root.getChildrenAsJSX()).toEqual('first: a0, second: b0');
1430
-
1431
- await act(async () => {
1432
- // Switch the second getSnapshot to also read from A
1433
- root.render(
1434
- <App
1435
- getSnapshotFirst={getSnapshotA}
1436
- getSnapshotSecond={getSnapshotA}
1437
- />,
1438
- );
1439
- // Render and finish the tree, but yield right after paint, before
1440
- // the passive effects have fired.
1441
- expect(Scheduler).toFlushUntilNextPaint([]);
1442
-
1443
- // Now mutate A. Both hooks should update.
1444
- // This is at high priority so that it doesn't get batched with default
1445
- // priority updates that might fire during the passive effect
1446
- await act(async () => {
1447
- ReactNoop.discreteUpdates(() => {
1448
- mutateA('a1');
1449
- });
1450
- });
1451
-
1452
- expect(root).toMatchRenderedOutput('first: a1, second: a1');
1453
- });
1454
-
1455
- expect(root.getChildrenAsJSX()).toEqual('first: a1, second: a1');
1456
- });
1457
-
1458
- it(
1459
- 'if source is mutated after initial read but before subscription is set ' +
1460
- 'up, should still entangle all pending mutations even if snapshot of ' +
1461
- 'new subscription happens to match',
1462
- async () => {
1463
- const source = createSource({
1464
- a: 'a0',
1465
- b: 'b0',
1466
- });
1467
- const mutableSource = createMutableSource(source, param => param.version);
1468
-
1469
- const getSnapshotA = () => source.value.a;
1470
- const getSnapshotB = () => source.value.b;
1471
-
1472
- function mutateA(newA) {
1473
- source.value = {
1474
- ...source.value,
1475
- a: newA,
1476
- };
1477
- }
1478
-
1479
- function mutateB(newB) {
1480
- source.value = {
1481
- ...source.value,
1482
- b: newB,
1483
- };
1484
- }
1485
-
1486
- function Read({getSnapshot}) {
1487
- const value = useMutableSource(
1488
- mutableSource,
1489
- getSnapshot,
1490
- defaultSubscribe,
1491
- );
1492
- Scheduler.unstable_yieldValue(value);
1493
- return value;
1494
- }
1495
-
1496
- function Text({text}) {
1497
- Scheduler.unstable_yieldValue(text);
1498
- return text;
1499
- }
1500
-
1501
- const root = ReactNoop.createRoot();
1502
- await act(async () => {
1503
- root.render(
1504
- <>
1505
- <Read getSnapshot={getSnapshotA} />
1506
- </>,
1507
- );
1508
- });
1509
- expect(Scheduler).toHaveYielded(['a0']);
1510
- expect(root).toMatchRenderedOutput('a0');
1511
-
1512
- await act(async () => {
1513
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
1514
- React.startTransition(() => {
1515
- root.render(
1516
- <>
1517
- <Read getSnapshot={getSnapshotA} />
1518
- <Read getSnapshot={getSnapshotB} />
1519
- <Text text="c" />
1520
- </>,
1521
- );
1522
- });
1523
- } else {
1524
- root.render(
1525
- <>
1526
- <Read getSnapshot={getSnapshotA} />
1527
- <Read getSnapshot={getSnapshotB} />
1528
- <Text text="c" />
1529
- </>,
1530
- );
1531
- }
1532
-
1533
- expect(Scheduler).toFlushAndYieldThrough(['a0', 'b0']);
1534
- // Mutate in an event. This schedules a subscription update on a, which
1535
- // already mounted, but not b, which hasn't subscribed yet.
1536
- mutateA('a1');
1537
- mutateB('b1');
1538
-
1539
- // Mutate again at lower priority. This will schedule another subscription
1540
- // update on a, but not b. When b mounts and subscriptions, the value it
1541
- // read during render will happen to match the latest value. But it should
1542
- // still entangle the updates to prevent the previous update (a1) from
1543
- // rendering by itself.
1544
- React.startTransition(() => {
1545
- mutateA('a0');
1546
- mutateB('b0');
1547
- });
1548
- // Finish the current render
1549
- expect(Scheduler).toFlushUntilNextPaint(['c']);
1550
- // a0 will re-render because of the mutation update. But it should show
1551
- // the latest value, not the intermediate one, to avoid tearing with b.
1552
- expect(Scheduler).toFlushUntilNextPaint(['a0']);
1553
-
1554
- expect(root).toMatchRenderedOutput('a0b0c');
1555
- // We should be done.
1556
- expect(Scheduler).toFlushAndYield([]);
1557
- expect(root).toMatchRenderedOutput('a0b0c');
1558
- });
1559
- },
1560
- );
1561
-
1562
- it('warns about functions being used as snapshot values', async () => {
1563
- const source = createSource(() => 'a');
1564
- const mutableSource = createMutableSource(source, param => param.version);
1565
-
1566
- const getSnapshot = () => source.value;
1567
-
1568
- function Read() {
1569
- const fn = useMutableSource(mutableSource, getSnapshot, defaultSubscribe);
1570
- const value = fn();
1571
- Scheduler.unstable_yieldValue(value);
1572
- return value;
1573
- }
1574
-
1575
- const root = ReactNoop.createRoot();
1576
- await act(async () => {
1577
- root.render(
1578
- <>
1579
- <Read />
1580
- </>,
1581
- );
1582
- expect(() => expect(Scheduler).toFlushAndYield(['a'])).toErrorDev(
1583
- 'Mutable source should not return a function as the snapshot value.',
1584
- );
1585
- });
1586
- expect(root).toMatchRenderedOutput('a');
1587
- });
1588
-
1589
- it('getSnapshot changes and then source is mutated during interleaved event', async () => {
1590
- const {useEffect} = React;
1591
-
1592
- const source = createComplexSource('1', '2');
1593
- const mutableSource = createMutableSource(source, param => param.version);
1594
-
1595
- // Subscribe to part of the store.
1596
- const getSnapshotA = s => s.valueA;
1597
- const subscribeA = (s, callback) => s.subscribeA(callback);
1598
- const configA = [getSnapshotA, subscribeA];
1599
-
1600
- const getSnapshotB = s => s.valueB;
1601
- const subscribeB = (s, callback) => s.subscribeB(callback);
1602
- const configB = [getSnapshotB, subscribeB];
1603
-
1604
- function App({parentConfig, childConfig}) {
1605
- const [getSnapshot, subscribe] = parentConfig;
1606
- const parentValue = useMutableSource(
1607
- mutableSource,
1608
- getSnapshot,
1609
- subscribe,
1610
- );
1611
-
1612
- Scheduler.unstable_yieldValue('Parent: ' + parentValue);
1613
-
1614
- return (
1615
- <Child
1616
- parentConfig={parentConfig}
1617
- childConfig={childConfig}
1618
- parentValue={parentValue}
1619
- />
1620
- );
1621
- }
1622
-
1623
- function Child({parentConfig, childConfig, parentValue}) {
1624
- const [getSnapshot, subscribe] = childConfig;
1625
- const childValue = useMutableSource(
1626
- mutableSource,
1627
- getSnapshot,
1628
- subscribe,
1629
- );
1630
-
1631
- Scheduler.unstable_yieldValue('Child: ' + childValue);
1632
-
1633
- let result = `${parentValue}, ${childValue}`;
1634
-
1635
- if (parentConfig === childConfig) {
1636
- // When both components read using the same config, the two values
1637
- // must be consistent.
1638
- if (parentValue !== childValue) {
1639
- result = 'Oops, tearing!';
1640
- }
1641
- }
1642
-
1643
- useEffect(() => {
1644
- Scheduler.unstable_yieldValue('Commit: ' + result);
1645
- }, [result]);
1646
-
1647
- return result;
1648
- }
1649
-
1650
- const root = ReactNoop.createRoot();
1651
- await act(async () => {
1652
- root.render(<App parentConfig={configA} childConfig={configB} />);
1653
- });
1654
- expect(Scheduler).toHaveYielded(['Parent: 1', 'Child: 2', 'Commit: 1, 2']);
1655
-
1656
- await act(async () => {
1657
- // Switch the parent and the child to read using the same config
1658
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
1659
- React.startTransition(() => {
1660
- root.render(<App parentConfig={configB} childConfig={configB} />);
1661
- });
1662
- } else {
1663
- root.render(<App parentConfig={configB} childConfig={configB} />);
1664
- }
1665
- // Start rendering the parent, but yield before rendering the child
1666
- expect(Scheduler).toFlushAndYieldThrough(['Parent: 2']);
1667
-
1668
- // Mutate the config. This is at lower priority so that 1) to make sure
1669
- // it doesn't happen to get batched with the in-progress render, and 2)
1670
- // so it doesn't interrupt the in-progress render.
1671
- React.startTransition(() => {
1672
- source.valueB = '3';
1673
- });
1674
-
1675
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
1676
- // In default sync mode, all of the updates flush sync.
1677
- expect(Scheduler).toFlushAndYieldThrough([
1678
- // The partial render completes
1679
- 'Child: 2',
1680
- 'Commit: 2, 2',
1681
- 'Parent: 3',
1682
- 'Child: 3',
1683
- ]);
1684
-
1685
- expect(Scheduler).toFlushAndYield([
1686
- // Now finish the rest of the update
1687
- 'Commit: 3, 3',
1688
- ]);
1689
- } else {
1690
- expect(Scheduler).toFlushAndYieldThrough([
1691
- // The partial render completes
1692
- 'Child: 2',
1693
- 'Commit: 2, 2',
1694
- ]);
1695
-
1696
- // Now there are two pending mutations at different priorities. But they
1697
- // both read the same version of the mutable source, so we must render
1698
- // them simultaneously.
1699
- //
1700
- expect(Scheduler).toFlushAndYieldThrough([
1701
- 'Parent: 3',
1702
- // Demonstrates that we can yield here
1703
- ]);
1704
- expect(Scheduler).toFlushAndYield([
1705
- // Now finish the rest of the update
1706
- 'Child: 3',
1707
- 'Commit: 3, 3',
1708
- ]);
1709
- }
1710
- });
1711
- });
1712
-
1713
- it('should not tear with newly mounted component when updates were scheduled at a lower priority', async () => {
1714
- const source = createSource('one');
1715
- const mutableSource = createMutableSource(source, param => param.version);
1716
-
1717
- let committedA = null;
1718
- let committedB = null;
1719
-
1720
- const onRender = () => {
1721
- if (committedB !== null) {
1722
- expect(committedA).toBe(committedB);
1723
- }
1724
- };
1725
-
1726
- function ComponentA() {
1727
- const snapshot = useMutableSource(
1728
- mutableSource,
1729
- defaultGetSnapshot,
1730
- defaultSubscribe,
1731
- );
1732
- Scheduler.unstable_yieldValue(`a:${snapshot}`);
1733
- React.useEffect(() => {
1734
- committedA = snapshot;
1735
- }, [snapshot]);
1736
- return <div>{`a:${snapshot}`}</div>;
1737
- }
1738
- function ComponentB() {
1739
- const snapshot = useMutableSource(
1740
- mutableSource,
1741
- defaultGetSnapshot,
1742
- defaultSubscribe,
1743
- );
1744
- Scheduler.unstable_yieldValue(`b:${snapshot}`);
1745
- React.useEffect(() => {
1746
- committedB = snapshot;
1747
- }, [snapshot]);
1748
- return <div>{`b:${snapshot}`}</div>;
1749
- }
1750
-
1751
- // Mount ComponentA with data version 1
1752
- act(() => {
1753
- ReactNoop.render(
1754
- <React.Profiler id="root" onRender={onRender}>
1755
- <ComponentA />
1756
- </React.Profiler>,
1757
- () => Scheduler.unstable_yieldValue('Sync effect'),
1758
- );
1759
- });
1760
- expect(Scheduler).toHaveYielded(['a:one', 'Sync effect']);
1761
- expect(source.listenerCount).toBe(1);
1762
-
1763
- // Mount ComponentB with version 1 (but don't commit it)
1764
- act(() => {
1765
- ReactNoop.render(
1766
- <React.Profiler id="root" onRender={onRender}>
1767
- <ComponentA />
1768
- <ComponentB />
1769
- </React.Profiler>,
1770
- () => Scheduler.unstable_yieldValue('Sync effect'),
1771
- );
1772
- expect(Scheduler).toFlushAndYieldThrough([
1773
- 'a:one',
1774
- 'b:one',
1775
- 'Sync effect',
1776
- ]);
1777
- expect(source.listenerCount).toBe(1);
1778
-
1779
- // Mutate -> schedule update for ComponentA
1780
- React.startTransition(() => {
1781
- source.value = 'two';
1782
- });
1783
-
1784
- // Commit ComponentB -> notice the change and schedule an update for ComponentB
1785
- expect(Scheduler).toFlushAndYield(['a:two', 'b:two']);
1786
- expect(source.listenerCount).toBe(2);
1787
- });
1788
- });
1789
-
1790
- if (__DEV__) {
1791
- // See https://github.com/facebook/react/issues/19948
1792
- describe('side effects detection', () => {
1793
- it('should throw if a mutable source is mutated during render', () => {
1794
- const source = createSource(0);
1795
- const mutableSource = createMutableSource(
1796
- source,
1797
- param => param.version,
1798
- );
1799
-
1800
- let mutatedValueInRender = 1;
1801
- function MutateDuringRead() {
1802
- const value = useMutableSource(
1803
- mutableSource,
1804
- defaultGetSnapshot,
1805
- defaultSubscribe,
1806
- );
1807
- Scheduler.unstable_yieldValue('MutateDuringRead:' + value);
1808
- // Note that mutating an external value during render is a side effect and is not supported.
1809
- source.value = mutatedValueInRender++;
1810
- return null;
1811
- }
1812
-
1813
- // TODO The mechanism for this type of detection relies on StrictMode double rendering.
1814
- expect(() => {
1815
- act(() => {
1816
- ReactNoop.render(
1817
- <React.StrictMode>
1818
- <MutateDuringRead />
1819
- </React.StrictMode>,
1820
- );
1821
- });
1822
- }).toThrow(
1823
- 'A mutable source was mutated while the MutateDuringRead component ' +
1824
- 'was rendering. This is not supported. Move any mutations into ' +
1825
- 'event handlers or effects.',
1826
- );
1827
-
1828
- expect(Scheduler).toHaveYielded([
1829
- // First attempt
1830
- 'MutateDuringRead:0',
1831
-
1832
- // Synchronous retry
1833
- 'MutateDuringRead:1',
1834
- ]);
1835
- });
1836
-
1837
- it('should throw if a mutable source is mutated during render (legacy mode)', () => {
1838
- const source = createSource('initial');
1839
- const mutableSource = createMutableSource(
1840
- source,
1841
- param => param.version,
1842
- );
1843
-
1844
- function MutateDuringRead() {
1845
- const value = useMutableSource(
1846
- mutableSource,
1847
- defaultGetSnapshot,
1848
- defaultSubscribe,
1849
- );
1850
- Scheduler.unstable_yieldValue('MutateDuringRead:' + value);
1851
- // Note that mutating an external value during render is a side effect and is not supported.
1852
- if (value === 'initial') {
1853
- source.value = 'updated';
1854
- }
1855
- return null;
1856
- }
1857
-
1858
- expect(() => {
1859
- act(() => {
1860
- ReactNoop.renderLegacySyncRoot(
1861
- <React.StrictMode>
1862
- <MutateDuringRead />
1863
- </React.StrictMode>,
1864
- );
1865
- });
1866
- }).toThrow(
1867
- 'A mutable source was mutated while the MutateDuringRead component ' +
1868
- 'was rendering. This is not supported. Move any mutations into ' +
1869
- 'event handlers or effects.',
1870
- );
1871
-
1872
- expect(Scheduler).toHaveYielded(['MutateDuringRead:initial']);
1873
- });
1874
-
1875
- it('should not misidentify mutations after render as side effects', async () => {
1876
- const source = createSource('initial');
1877
- const mutableSource = createMutableSource(
1878
- source,
1879
- param => param.version,
1880
- );
1881
-
1882
- function MutateDuringRead() {
1883
- const value = useMutableSource(
1884
- mutableSource,
1885
- defaultGetSnapshot,
1886
- defaultSubscribe,
1887
- );
1888
- Scheduler.unstable_yieldValue('MutateDuringRead:' + value);
1889
- return null;
1890
- }
1891
-
1892
- await act(async () => {
1893
- ReactNoop.renderLegacySyncRoot(
1894
- <React.StrictMode>
1895
- <MutateDuringRead />
1896
- </React.StrictMode>,
1897
- );
1898
- });
1899
- expect(Scheduler).toHaveYielded(['MutateDuringRead:initial']);
1900
-
1901
- await act(async () => {
1902
- source.value = 'updated';
1903
- });
1904
- expect(Scheduler).toHaveYielded(['MutateDuringRead:updated']);
1905
- });
1906
- });
1907
-
1908
- describe('dev warnings', () => {
1909
- it('should warn if the subscribe function does not return an unsubscribe function', () => {
1910
- const source = createSource('one');
1911
- const mutableSource = createMutableSource(
1912
- source,
1913
- param => param.version,
1914
- );
1915
-
1916
- const brokenSubscribe = () => {};
1917
-
1918
- expect(() => {
1919
- act(() => {
1920
- ReactNoop.render(
1921
- <Component
1922
- label="only"
1923
- getSnapshot={defaultGetSnapshot}
1924
- mutableSource={mutableSource}
1925
- subscribe={brokenSubscribe}
1926
- />,
1927
- );
1928
- });
1929
- }).toErrorDev(
1930
- 'Mutable source subscribe function must return an unsubscribe function.',
1931
- );
1932
- });
1933
-
1934
- it('should error if multiple renderers of the same type use a mutable source at the same time', () => {
1935
- const source = createSource('one');
1936
- const mutableSource = createMutableSource(
1937
- source,
1938
- param => param.version,
1939
- );
1940
-
1941
- act(() => {
1942
- // Start a render that uses the mutable source.
1943
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
1944
- React.startTransition(() => {
1945
- ReactNoop.render(
1946
- <>
1947
- <Component
1948
- label="a"
1949
- getSnapshot={defaultGetSnapshot}
1950
- mutableSource={mutableSource}
1951
- subscribe={defaultSubscribe}
1952
- />
1953
- <Component
1954
- label="b"
1955
- getSnapshot={defaultGetSnapshot}
1956
- mutableSource={mutableSource}
1957
- subscribe={defaultSubscribe}
1958
- />
1959
- </>,
1960
- );
1961
- });
1962
- } else {
1963
- ReactNoop.render(
1964
- <>
1965
- <Component
1966
- label="a"
1967
- getSnapshot={defaultGetSnapshot}
1968
- mutableSource={mutableSource}
1969
- subscribe={defaultSubscribe}
1970
- />
1971
- <Component
1972
- label="b"
1973
- getSnapshot={defaultGetSnapshot}
1974
- mutableSource={mutableSource}
1975
- subscribe={defaultSubscribe}
1976
- />
1977
- </>,
1978
- );
1979
- }
1980
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
1981
-
1982
- const PrevScheduler = Scheduler;
1983
-
1984
- // Get a new copy of ReactNoop.
1985
- loadModules();
1986
-
1987
- spyOnDev(console, 'error');
1988
-
1989
- // Use the mutablesource again but with a different renderer.
1990
- ReactNoop.render(
1991
- <Component
1992
- label="c"
1993
- getSnapshot={defaultGetSnapshot}
1994
- mutableSource={mutableSource}
1995
- subscribe={defaultSubscribe}
1996
- />,
1997
- );
1998
- expect(Scheduler).toFlushAndYieldThrough(['c:one']);
1999
-
2000
- expect(console.error.calls.argsFor(0)[0]).toContain(
2001
- 'Detected multiple renderers concurrently rendering the ' +
2002
- 'same mutable source. This is currently unsupported.',
2003
- );
2004
-
2005
- // TODO (useMutableSource) Act will automatically flush remaining work from render 1,
2006
- // but at this point something in the hooks dispatcher has been broken by jest.resetModules()
2007
- // Figure out what this is and remove this catch.
2008
- expect(() =>
2009
- PrevScheduler.unstable_flushAllWithoutAsserting(),
2010
- ).toThrow('Invalid hook call');
2011
- });
2012
- });
2013
-
2014
- it('should error if multiple renderers of the same type use a mutable source at the same time with mutation between', () => {
2015
- const source = createSource('one');
2016
- const mutableSource = createMutableSource(
2017
- source,
2018
- param => param.version,
2019
- );
2020
-
2021
- act(() => {
2022
- // Start a render that uses the mutable source.
2023
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
2024
- React.startTransition(() => {
2025
- ReactNoop.render(
2026
- <>
2027
- <Component
2028
- label="a"
2029
- getSnapshot={defaultGetSnapshot}
2030
- mutableSource={mutableSource}
2031
- subscribe={defaultSubscribe}
2032
- />
2033
- <Component
2034
- label="b"
2035
- getSnapshot={defaultGetSnapshot}
2036
- mutableSource={mutableSource}
2037
- subscribe={defaultSubscribe}
2038
- />
2039
- </>,
2040
- );
2041
- });
2042
- } else {
2043
- ReactNoop.render(
2044
- <>
2045
- <Component
2046
- label="a"
2047
- getSnapshot={defaultGetSnapshot}
2048
- mutableSource={mutableSource}
2049
- subscribe={defaultSubscribe}
2050
- />
2051
- <Component
2052
- label="b"
2053
- getSnapshot={defaultGetSnapshot}
2054
- mutableSource={mutableSource}
2055
- subscribe={defaultSubscribe}
2056
- />
2057
- </>,
2058
- );
2059
- }
2060
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
2061
-
2062
- const PrevScheduler = Scheduler;
2063
-
2064
- // Get a new copy of ReactNoop.
2065
- loadModules();
2066
-
2067
- spyOnDev(console, 'error');
2068
-
2069
- // Mutate before the new render reads from the source.
2070
- source.value = 'two';
2071
-
2072
- // Use the mutablesource again but with a different renderer.
2073
- ReactNoop.render(
2074
- <Component
2075
- label="c"
2076
- getSnapshot={defaultGetSnapshot}
2077
- mutableSource={mutableSource}
2078
- subscribe={defaultSubscribe}
2079
- />,
2080
- );
2081
- expect(Scheduler).toFlushAndYieldThrough(['c:two']);
2082
-
2083
- expect(console.error.calls.argsFor(0)[0]).toContain(
2084
- 'Detected multiple renderers concurrently rendering the ' +
2085
- 'same mutable source. This is currently unsupported.',
2086
- );
2087
-
2088
- // TODO (useMutableSource) Act will automatically flush remaining work from render 1,
2089
- // but at this point something in the hooks dispatcher has been broken by jest.resetModules()
2090
- // Figure out what this is and remove this catch.
2091
- expect(() =>
2092
- PrevScheduler.unstable_flushAllWithoutAsserting(),
2093
- ).toThrow('Invalid hook call');
2094
- });
2095
- });
2096
- });
2097
- }
2098
-});
packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js
deleted
-451
@@ -1,451 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @emails react-core
8
- */
9
-
10
-'use strict';
11
-
12
-let React;
13
-let ReactDOM;
14
-let ReactDOMServer;
15
-let Scheduler;
16
-let act;
17
-let createMutableSource;
18
-let useMutableSource;
19
-
20
-describe('useMutableSourceHydration', () => {
21
- beforeEach(() => {
22
- jest.resetModules();
23
-
24
- React = require('react');
25
- ReactDOM = require('react-dom');
26
- ReactDOMServer = require('react-dom/server');
27
- Scheduler = require('scheduler');
28
-
29
- act = require('jest-react').act;
30
-
31
- // Stable entrypoints export with "unstable_" prefix.
32
- createMutableSource =
33
- React.createMutableSource || React.unstable_createMutableSource;
34
- useMutableSource =
35
- React.useMutableSource || React.unstable_useMutableSource;
36
- });
37
-
38
- function dispatchAndSetCurrentEvent(el, event) {
39
- try {
40
- window.event = event;
41
- el.dispatchEvent(event);
42
- } finally {
43
- window.event = undefined;
44
- }
45
- }
46
-
47
- const defaultGetSnapshot = source => source.value;
48
- const defaultSubscribe = (source, callback) => source.subscribe(callback);
49
-
50
- function createComplexSource(initialValueA, initialValueB) {
51
- const callbacksA = [];
52
- const callbacksB = [];
53
- let revision = 0;
54
- let valueA = initialValueA;
55
- let valueB = initialValueB;
56
-
57
- const subscribeHelper = (callbacks, callback) => {
58
- if (callbacks.indexOf(callback) < 0) {
59
- callbacks.push(callback);
60
- }
61
- return () => {
62
- const index = callbacks.indexOf(callback);
63
- if (index >= 0) {
64
- callbacks.splice(index, 1);
65
- }
66
- };
67
- };
68
-
69
- return {
70
- subscribeA(callback) {
71
- return subscribeHelper(callbacksA, callback);
72
- },
73
- subscribeB(callback) {
74
- return subscribeHelper(callbacksB, callback);
75
- },
76
-
77
- get listenerCountA() {
78
- return callbacksA.length;
79
- },
80
- get listenerCountB() {
81
- return callbacksB.length;
82
- },
83
-
84
- set valueA(newValue) {
85
- revision++;
86
- valueA = newValue;
87
- callbacksA.forEach(callback => callback());
88
- },
89
- get valueA() {
90
- return valueA;
91
- },
92
-
93
- set valueB(newValue) {
94
- revision++;
95
- valueB = newValue;
96
- callbacksB.forEach(callback => callback());
97
- },
98
- get valueB() {
99
- return valueB;
100
- },
101
-
102
- get version() {
103
- return revision;
104
- },
105
- };
106
- }
107
-
108
- function createSource(initialValue) {
109
- const callbacks = [];
110
- let revision = 0;
111
- let value = initialValue;
112
- return {
113
- subscribe(callback) {
114
- if (callbacks.indexOf(callback) < 0) {
115
- callbacks.push(callback);
116
- }
117
- return () => {
118
- const index = callbacks.indexOf(callback);
119
- if (index >= 0) {
120
- callbacks.splice(index, 1);
121
- }
122
- };
123
- },
124
- get listenerCount() {
125
- return callbacks.length;
126
- },
127
- set value(newValue) {
128
- revision++;
129
- value = newValue;
130
- callbacks.forEach(callback => callback());
131
- },
132
- get value() {
133
- return value;
134
- },
135
- get version() {
136
- return revision;
137
- },
138
- };
139
- }
140
-
141
- function Component({getSnapshot, label, mutableSource, subscribe}) {
142
- const snapshot = useMutableSource(mutableSource, getSnapshot, subscribe);
143
- Scheduler.unstable_yieldValue(`${label}:${snapshot}`);
144
- return <div>{`${label}:${snapshot}`}</div>;
145
- }
146
-
147
- it('should render and hydrate', () => {
148
- const source = createSource('one');
149
- const mutableSource = createMutableSource(source, param => param.version);
150
-
151
- function TestComponent() {
152
- return (
153
- <Component
154
- label="only"
155
- getSnapshot={defaultGetSnapshot}
156
- mutableSource={mutableSource}
157
- subscribe={defaultSubscribe}
158
- />
159
- );
160
- }
161
-
162
- const container = document.createElement('div');
163
- document.body.appendChild(container);
164
-
165
- const htmlString = ReactDOMServer.renderToString(<TestComponent />);
166
- container.innerHTML = htmlString;
167
- expect(Scheduler).toHaveYielded(['only:one']);
168
- expect(source.listenerCount).toBe(0);
169
-
170
- const root = ReactDOM.createRoot(container, {
171
- hydrate: true,
172
- hydrationOptions: {
173
- mutableSources: [mutableSource],
174
- },
175
- });
176
- act(() => {
177
- root.render(<TestComponent />);
178
- });
179
- expect(Scheduler).toHaveYielded(['only:one']);
180
- expect(source.listenerCount).toBe(1);
181
- });
182
-
183
- it('should detect a tear before hydrating a component', () => {
184
- const source = createSource('one');
185
- const mutableSource = createMutableSource(source, param => param.version);
186
-
187
- function TestComponent() {
188
- return (
189
- <Component
190
- label="only"
191
- getSnapshot={defaultGetSnapshot}
192
- mutableSource={mutableSource}
193
- subscribe={defaultSubscribe}
194
- />
195
- );
196
- }
197
-
198
- const container = document.createElement('div');
199
- document.body.appendChild(container);
200
-
201
- const htmlString = ReactDOMServer.renderToString(<TestComponent />);
202
- container.innerHTML = htmlString;
203
- expect(Scheduler).toHaveYielded(['only:one']);
204
- expect(source.listenerCount).toBe(0);
205
-
206
- const root = ReactDOM.createRoot(container, {
207
- hydrate: true,
208
- hydrationOptions: {
209
- mutableSources: [mutableSource],
210
- },
211
- });
212
- expect(() => {
213
- act(() => {
214
- root.render(<TestComponent />);
215
-
216
- source.value = 'two';
217
- });
218
- }).toErrorDev(
219
- 'Warning: An error occurred during hydration. ' +
220
- 'The server HTML was replaced with client content in <div>.',
221
- {withoutStack: true},
222
- );
223
- expect(Scheduler).toHaveYielded(['only:two']);
224
- expect(source.listenerCount).toBe(1);
225
- });
226
-
227
- it('should detect a tear between hydrating components', () => {
228
- const source = createSource('one');
229
- const mutableSource = createMutableSource(source, param => param.version);
230
-
231
- function TestComponent() {
232
- return (
233
- <>
234
- <Component
235
- label="a"
236
- getSnapshot={defaultGetSnapshot}
237
- mutableSource={mutableSource}
238
- subscribe={defaultSubscribe}
239
- />
240
- <Component
241
- label="b"
242
- getSnapshot={defaultGetSnapshot}
243
- mutableSource={mutableSource}
244
- subscribe={defaultSubscribe}
245
- />
246
- </>
247
- );
248
- }
249
-
250
- const container = document.createElement('div');
251
- document.body.appendChild(container);
252
-
253
- const htmlString = ReactDOMServer.renderToString(<TestComponent />);
254
- container.innerHTML = htmlString;
255
- expect(Scheduler).toHaveYielded(['a:one', 'b:one']);
256
- expect(source.listenerCount).toBe(0);
257
-
258
- const root = ReactDOM.createRoot(container, {
259
- hydrate: true,
260
- hydrationOptions: {
261
- mutableSources: [mutableSource],
262
- },
263
- });
264
- expect(() => {
265
- act(() => {
266
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
267
- React.startTransition(() => {
268
- root.render(<TestComponent />);
269
- });
270
- } else {
271
- root.render(<TestComponent />);
272
- }
273
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
274
- source.value = 'two';
275
- });
276
- }).toErrorDev(
277
- 'Warning: An error occurred during hydration. ' +
278
- 'The server HTML was replaced with client content in <div>.',
279
- {withoutStack: true},
280
- );
281
- expect(Scheduler).toHaveYielded(['a:two', 'b:two']);
282
- expect(source.listenerCount).toBe(2);
283
- });
284
-
285
- it('should detect a tear between hydrating components reading from different parts of a source', () => {
286
- const source = createComplexSource('a:one', 'b:one');
287
- const mutableSource = createMutableSource(source, param => param.version);
288
-
289
- // Subscribe to part of the store.
290
- const getSnapshotA = s => s.valueA;
291
- const subscribeA = (s, callback) => s.subscribeA(callback);
292
- const getSnapshotB = s => s.valueB;
293
- const subscribeB = (s, callback) => s.subscribeB(callback);
294
-
295
- const container = document.createElement('div');
296
- document.body.appendChild(container);
297
-
298
- const htmlString = ReactDOMServer.renderToString(
299
- <>
300
- <Component
301
- label="0"
302
- getSnapshot={getSnapshotA}
303
- mutableSource={mutableSource}
304
- subscribe={subscribeA}
305
- />
306
- <Component
307
- label="1"
308
- getSnapshot={getSnapshotB}
309
- mutableSource={mutableSource}
310
- subscribe={subscribeB}
311
- />
312
- </>,
313
- );
314
- container.innerHTML = htmlString;
315
- expect(Scheduler).toHaveYielded(['0:a:one', '1:b:one']);
316
-
317
- const root = ReactDOM.createRoot(container, {
318
- hydrate: true,
319
- hydrationOptions: {
320
- mutableSources: [mutableSource],
321
- },
322
- });
323
- expect(() => {
324
- act(() => {
325
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
326
- React.startTransition(() => {
327
- root.render(
328
- <>
329
- <Component
330
- label="0"
331
- getSnapshot={getSnapshotA}
332
- mutableSource={mutableSource}
333
- subscribe={subscribeA}
334
- />
335
- <Component
336
- label="1"
337
- getSnapshot={getSnapshotB}
338
- mutableSource={mutableSource}
339
- subscribe={subscribeB}
340
- />
341
- </>,
342
- );
343
- });
344
- } else {
345
- root.render(
346
- <>
347
- <Component
348
- label="0"
349
- getSnapshot={getSnapshotA}
350
- mutableSource={mutableSource}
351
- subscribe={subscribeA}
352
- />
353
- <Component
354
- label="1"
355
- getSnapshot={getSnapshotB}
356
- mutableSource={mutableSource}
357
- subscribe={subscribeB}
358
- />
359
- </>,
360
- );
361
- }
362
- expect(Scheduler).toFlushAndYieldThrough(['0:a:one']);
363
- source.valueB = 'b:two';
364
- });
365
- }).toErrorDev(
366
- 'Warning: An error occurred during hydration. ' +
367
- 'The server HTML was replaced with client content in <div>.',
368
- {withoutStack: true},
369
- );
370
- expect(Scheduler).toHaveYielded(['0:a:one', '1:b:two']);
371
- });
372
-
373
- // @gate !enableSyncDefaultUpdates
374
- it('should detect a tear during a higher priority interruption', () => {
375
- const source = createSource('one');
376
- const mutableSource = createMutableSource(source, param => param.version);
377
-
378
- function Unrelated({flag}) {
379
- Scheduler.unstable_yieldValue(flag);
380
- return flag;
381
- }
382
-
383
- function TestComponent({flag}) {
384
- return (
385
- <>
386
- <Unrelated flag={flag} />
387
- <Component
388
- label="a"
389
- getSnapshot={defaultGetSnapshot}
390
- mutableSource={mutableSource}
391
- subscribe={defaultSubscribe}
392
- />
393
- </>
394
- );
395
- }
396
-
397
- const container = document.createElement('div');
398
- document.body.appendChild(container);
399
-
400
- const htmlString = ReactDOMServer.renderToString(
401
- <TestComponent flag={1} />,
402
- );
403
- container.innerHTML = htmlString;
404
- expect(Scheduler).toHaveYielded([1, 'a:one']);
405
- expect(source.listenerCount).toBe(0);
406
-
407
- const root = ReactDOM.createRoot(container, {
408
- hydrate: true,
409
- hydrationOptions: {
410
- mutableSources: [mutableSource],
411
- },
412
- });
413
-
414
- expect(() => {
415
- act(() => {
416
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
417
- React.startTransition(() => {
418
- root.render(<TestComponent flag={1} />);
419
- });
420
- } else {
421
- root.render(<TestComponent flag={1} />);
422
- }
423
- expect(Scheduler).toFlushAndYieldThrough([1]);
424
-
425
- // Render an update which will be higher priority than the hydration.
426
- // We can do this by scheduling the update inside a mouseover event.
427
- const arbitraryElement = document.createElement('div');
428
- const mouseOverEvent = document.createEvent('MouseEvents');
429
- mouseOverEvent.initEvent('mouseover', true, true);
430
- arbitraryElement.addEventListener('mouseover', () => {
431
- root.render(<TestComponent flag={2} />);
432
- });
433
- dispatchAndSetCurrentEvent(arbitraryElement, mouseOverEvent);
434
-
435
- expect(Scheduler).toFlushAndYieldThrough([2]);
436
-
437
- source.value = 'two';
438
- });
439
- }).toErrorDev(
440
- [
441
- 'Warning: An error occurred during hydration. ' +
442
- 'The server HTML was replaced with client content in <div>.',
443
-
444
- 'Warning: Text content did not match. Server: "1" Client: "2"',
445
- ],
446
- {withoutStack: 1},
447
- );
448
- expect(Scheduler).toHaveYielded([2, 'a:two']);
449
- expect(source.listenerCount).toBe(1);
450
- });
451
-});
packages/react-server/src/ReactFizzHooks.js
+1
-20
@@ -9,12 +9,7 @@
9
10
import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
11
12
-import type {
13
- MutableSource,
14
- MutableSourceGetSnapshotFn,
15
- MutableSourceSubscribeFn,
16
- ReactContext,
17
-} from 'shared/ReactTypes';
12
+import type {ReactContext} from 'shared/ReactTypes';
13
14
import type {ResponseState, OpaqueIDType} from './ReactServerFormatConfig';
15
@@ -449,18 +444,6 @@ export function useCallback<T>(
444
return useMemo(() => callback, deps);
445
}
446
452
-// TODO Decide on how to implement this hook for server rendering.
453
-// If a mutation occurs during render, consider triggering a Suspense boundary
454
-// and falling back to client rendering.
455
-function useMutableSource<Source, Snapshot>(
456
- source: MutableSource<Source>,
457
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
458
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
459
-): Snapshot {
460
- resolveCurrentlyRenderingComponent();
461
- return getSnapshot(source._source);
462
-}
463
-
447
function useSyncExternalStore<T>(
448
subscribe: (() => void) => () => void,
449
getSnapshot: () => T,
@@ -515,8 +498,6 @@ export const Dispatcher: DispatcherType = {
498
useDeferredValue,
499
useTransition,
500
useOpaqueIdentifier,
518
- // Subscriptions are not setup in a server environment.
519
- useMutableSource,
501
useSyncExternalStore,
502
};
503
packages/react-server/src/ReactFlightServer.js
-1
@@ -827,7 +827,6 @@ const Dispatcher: DispatcherType = {
827
useImperativeHandle: (unsupportedHook: any),
828
useEffect: (unsupportedHook: any),
829
useOpaqueIdentifier: (unsupportedHook: any),
830
- useMutableSource: (unsupportedHook: any),
830
useSyncExternalStore: (unsupportedHook: any),
831
useCacheRefresh(): <T>(?() => T, ?T) => void {
832
return unsupportedRefresh;
packages/react-suspense-test-utils/src/ReactSuspenseTestUtils.js
-1
@@ -44,7 +44,6 @@ export function waitForSuspense<T>(fn: () => T): Promise<T> {
44
useDeferredValue: unsupported,
45
useTransition: unsupported,
46
useOpaqueIdentifier: unsupported,
47
- useMutableSource: unsupported,
47
useSyncExternalStore: unsupported,
48
useCacheRefresh: unsupported,
49
};
packages/react/index.classic.fb.js
-4
@@ -23,8 +23,6 @@ export {
23
createContext,
24
createElement,
25
createFactory,
26
- createMutableSource,
27
- createMutableSource as unstable_createMutableSource,
26
createRef,
27
forwardRef,
28
isValidElement,
@@ -50,8 +48,6 @@ export {
48
useLayoutEffect,
49
unstable_useInsertionEffect,
50
useMemo,
53
- useMutableSource,
54
- useMutableSource as unstable_useMutableSource,
51
useSyncExternalStore,
52
useSyncExternalStore as unstable_useSyncExternalStore,
53
useReducer,
packages/react/index.experimental.js
-2
@@ -22,7 +22,6 @@ export {
22
createContext,
23
createElement,
24
createFactory,
25
- createMutableSource as unstable_createMutableSource,
25
createRef,
26
forwardRef,
27
isValidElement,
@@ -45,7 +44,6 @@ export {
44
unstable_useInsertionEffect,
45
useLayoutEffect,
46
useMemo,
48
- useMutableSource as unstable_useMutableSource,
47
useSyncExternalStore as unstable_useSyncExternalStore,
48
useReducer,
49
useRef,
packages/react/index.js
-2
@@ -46,7 +46,6 @@ export {
46
createContext,
47
createElement,
48
createFactory,
49
- createMutableSource,
49
createRef,
50
forwardRef,
51
isValidElement,
@@ -70,7 +69,6 @@ export {
69
unstable_useInsertionEffect,
70
useLayoutEffect,
71
useMemo,
73
- useMutableSource,
72
useSyncExternalStore,
73
useSyncExternalStore as unstable_useSyncExternalStore,
74
useReducer,
packages/react/index.modern.fb.js
-4
@@ -22,8 +22,6 @@ export {
22
cloneElement,
23
createContext,
24
createElement,
25
- createMutableSource,
26
- createMutableSource as unstable_createMutableSource,
25
createRef,
26
forwardRef,
27
isValidElement,
@@ -49,8 +47,6 @@ export {
47
unstable_useInsertionEffect,
48
useLayoutEffect,
49
useMemo,
52
- useMutableSource,
53
- useMutableSource as unstable_useMutableSource,
50
useSyncExternalStore,
51
useSyncExternalStore as unstable_useSyncExternalStore,
52
useReducer,
packages/react/index.stable.js
-2
@@ -22,7 +22,6 @@ export {
22
createContext,
23
createElement,
24
createFactory,
25
- createMutableSource as unstable_createMutableSource,
25
createRef,
26
forwardRef,
27
isValidElement,
@@ -38,7 +37,6 @@ export {
37
useImperativeHandle,
38
useLayoutEffect,
39
useMemo,
41
- useMutableSource as unstable_useMutableSource,
40
useReducer,
41
useRef,
42
useState,
packages/react/src/React.js
-4
@@ -44,7 +44,6 @@ import {
44
useInsertionEffect,
45
useLayoutEffect,
46
useMemo,
47
- useMutableSource,
47
useSyncExternalStore,
48
useReducer,
49
useRef,
@@ -59,7 +58,6 @@ import {
58
createFactoryWithValidation,
59
cloneElementWithValidation,
60
} from './ReactElementValidator';
62
-import {createMutableSource} from './ReactMutableSource';
61
import ReactSharedInternals from './ReactSharedInternals';
62
import {startTransition} from './ReactStartTransition';
63
import {act} from './ReactAct';
@@ -79,7 +77,6 @@ const Children = {
77
78
export {
79
Children,
82
- createMutableSource,
80
createRef,
81
Component,
82
PureComponent,
@@ -95,7 +92,6 @@ export {
92
useInsertionEffect as unstable_useInsertionEffect,
93
useLayoutEffect,
94
useMemo,
98
- useMutableSource,
95
useSyncExternalStore,
96
useReducer,
97
useRef,
packages/react/src/ReactHooks.js
+1
-15
@@ -8,12 +8,7 @@
8
*/
9
10
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
11
-import type {
12
- MutableSource,
13
- MutableSourceGetSnapshotFn,
14
- MutableSourceSubscribeFn,
15
- ReactContext,
16
-} from 'shared/ReactTypes';
11
+import type {ReactContext} from 'shared/ReactTypes';
12
import type {OpaqueIDType} from 'react-reconciler/src/ReactFiberHostConfig';
13
14
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
@@ -168,15 +163,6 @@ export function useOpaqueIdentifier(): OpaqueIDType | void {
163
return dispatcher.useOpaqueIdentifier();
164
}
165
171
-export function useMutableSource<Source, Snapshot>(
172
- source: MutableSource<Source>,
173
- getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
174
- subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
175
-): Snapshot {
176
- const dispatcher = resolveDispatcher();
177
- return dispatcher.useMutableSource(source, getSnapshot, subscribe);
178
-}
179
-
166
export function useSyncExternalStore<T>(
167
subscribe: (() => void) => () => void,
168
getSnapshot: () => T,
packages/react/src/ReactMutableSource.js
deleted
-34
@@ -1,34 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @flow
8
- */
9
-
10
-import type {MutableSource, MutableSourceGetVersionFn} from 'shared/ReactTypes';
11
-
12
-export function createMutableSource<Source: $NonMaybeType<mixed>>(
13
- source: Source,
14
- getVersion: MutableSourceGetVersionFn,
15
-): MutableSource<Source> {
16
- const mutableSource: MutableSource<Source> = {
17
- _getVersion: getVersion,
18
- _source: source,
19
- _workInProgressVersionPrimary: null,
20
- _workInProgressVersionSecondary: null,
21
- };
22
-
23
- if (__DEV__) {
24
- mutableSource._currentPrimaryRenderer = null;
25
- mutableSource._currentSecondaryRenderer = null;
26
-
27
- // Used to detect side effects that update a mutable source during render.
28
- // See https://github.com/facebook/react/issues/19948
29
- mutableSource._currentlyRenderingFiber = null;
30
- mutableSource._initialVersionAsOfFirstRender = null;
31
- }
32
-
33
- return mutableSource;
34
-}
packages/react/unstable-shared-subset.experimental.js
-2
@@ -17,7 +17,6 @@ export {
17
SuspenseList,
18
cloneElement,
19
createElement,
20
- createMutableSource as unstable_createMutableSource,
20
createRef,
21
forwardRef,
22
isValidElement,
@@ -33,7 +32,6 @@ export {
32
useDeferredValue,
33
useDeferredValue as unstable_useDeferredValue,
34
useMemo,
36
- useMutableSource as unstable_useMutableSource,
35
useTransition,
36
version,
37
} from './src/React';
packages/shared/ReactTypes.js
-50
@@ -101,56 +101,6 @@ export type ReactScopeInstance = {|
101
getChildContextValues: <T>(context: ReactContext<T>) => Array<T>,
102
|};
103
104
-// Mutable source version can be anything (e.g. number, string, immutable data structure)
105
-// so long as it changes every time any part of the source changes.
106
-export type MutableSourceVersion = $NonMaybeType<mixed>;
107
-
108
-export type MutableSourceGetSnapshotFn<
109
- Source: $NonMaybeType<mixed>,
110
- Snapshot,
111
-> = (source: Source) => Snapshot;
112
-
113
-export type MutableSourceSubscribeFn<Source: $NonMaybeType<mixed>, Snapshot> = (
114
- source: Source,
115
- callback: (snapshot: Snapshot) => void,
116
-) => () => void;
117
-
118
-export type MutableSourceGetVersionFn = (
119
- source: $NonMaybeType<mixed>,
120
-) => MutableSourceVersion;
121
-
122
-export type MutableSource<Source: $NonMaybeType<mixed>> = {|
123
- _source: Source,
124
-
125
- _getVersion: MutableSourceGetVersionFn,
126
-
127
- // Tracks the version of this source at the time it was most recently read.
128
- // Used to determine if a source is safe to read from before it has been subscribed to.
129
- // Version number is only used during mount,
130
- // since the mechanism for determining safety after subscription is expiration time.
131
- //
132
- // As a workaround to support multiple concurrent renderers,
133
- // we categorize some renderers as primary and others as secondary.
134
- // We only expect there to be two concurrent renderers at most:
135
- // React Native (primary) and Fabric (secondary);
136
- // React DOM (primary) and React ART (secondary).
137
- // Secondary renderers store their context values on separate fields.
138
- // We use the same approach for Context.
139
- _workInProgressVersionPrimary: null | MutableSourceVersion,
140
- _workInProgressVersionSecondary: null | MutableSourceVersion,
141
-
142
- // DEV only
143
- // Used to detect multiple renderers using the same mutable source.
144
- _currentPrimaryRenderer?: Object | null,
145
- _currentSecondaryRenderer?: Object | null,
146
-
147
- // DEV only
148
- // Used to detect side effects that update a mutable source during render.
149
- // See https://github.com/facebook/react/issues/19948
150
- _currentlyRenderingFiber?: Fiber | null,
151
- _initialVersionAsOfFirstRender?: MutableSourceVersion | null,
152
-|};
153
-
104
// The subset of a Thenable required by things thrown by Suspense.
105
// This doesn't require a value to be passed to either handler.
106
export interface Wakeable {