@samitouri / QOS-React-2 / commits / 8209de2695

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 {