@samitouri / QOS-React-2 / commits / 80d9a40114

Remove useMutableSource (#27011)

## Summary This PR cleans up `useMutableSource`. This has been blocked by a remaining dependency internally at Meta, but that has now been deleted. <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? --> ## How did you test this change? ``` yarn flow yarn lint yarn test --prod ``` <!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. How exactly did you verify that your PR solves the issue you wanted to solve? If you leave this empty, your PR will very likely be closed. -->

Noah Lemen committed Jun 27, 2023 at 12:45 UTC 80d9a40114bb43c07d021e8254790852f450bd2b
30 files changed +4 -3023
packages/react-debug-tools/src/ReactDebugHooks.js
-21
@@ -8,9 +8,6 @@
8 */
9
10 import type {
11 - MutableSource,
12 - MutableSourceGetSnapshotFn,
13 - MutableSourceSubscribeFn,
11 ReactContext,
12 ReactProviderType,
13 StartTransitionOptions,
@@ -273,23 +270,6 @@ function useMemo<T>(
270 return value;
271 }
272
276 -function useMutableSource<Source, Snapshot>(
277 - source: MutableSource<Source>,
278 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
279 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
280 -): Snapshot {
281 - // useMutableSource() composes multiple hooks internally.
282 - // Advance the current hook index the same number of times
283 - // so that subsequent hooks have the right memoized state.
284 - nextHook(); // MutableSource
285 - nextHook(); // State
286 - nextHook(); // Effect
287 - nextHook(); // Effect
288 - const value = getSnapshot(source._source);
289 - hookLog.push({primitive: 'MutableSource', stackError: new Error(), value});
290 - return value;
291 -}
292 -
273 function useSyncExternalStore<T>(
274 subscribe: (() => void) => () => void,
275 getSnapshot: () => T,
@@ -396,7 +376,6 @@ const Dispatcher: DispatcherType = {
376 useRef,
377 useState,
378 useTransition,
399 - useMutableSource,
379 useSyncExternalStore,
380 useDeferredValue,
381 useId,
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
-46
@@ -1036,52 +1036,6 @@ describe('ReactHooksInspectionIntegration', () => {
1036 ]);
1037 });
1038
1039 - // @gate enableUseMutableSource
1040 - it('should support composite useMutableSource hook', () => {
1041 - const createMutableSource =
1042 - React.createMutableSource || React.unstable_createMutableSource;
1043 - const useMutableSource =
1044 - React.useMutableSource || React.unstable_useMutableSource;
1045 -
1046 - const mutableSource = createMutableSource({}, () => 1);
1047 - function Foo(props) {
1048 - useMutableSource(
1049 - mutableSource,
1050 - () => 'snapshot',
1051 - () => {},
1052 - );
1053 - React.useMemo(() => 'memo', []);
1054 - React.useMemo(() => 'not used', []);
1055 - return <div />;
1056 - }
1057 - const renderer = ReactTestRenderer.create(<Foo />);
1058 - const childFiber = renderer.root.findByType(Foo)._currentFiber();
1059 - const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
1060 - expect(tree).toEqual([
1061 - {
1062 - id: 0,
1063 - isStateEditable: false,
1064 - name: 'MutableSource',
1065 - value: 'snapshot',
1066 - subHooks: [],
1067 - },
1068 - {
1069 - id: 1,
1070 - isStateEditable: false,
1071 - name: 'Memo',
1072 - value: 'memo',
1073 - subHooks: [],
1074 - },
1075 - {
1076 - id: 2,
1077 - isStateEditable: false,
1078 - name: 'Memo',
1079 - value: 'not used',
1080 - subHooks: [],
1081 - },
1082 - ]);
1083 - });
1084 -
1039 it('should support composite useSyncExternalStore hook', () => {
1040 const useSyncExternalStore = React.useSyncExternalStore;
1041 function Foo() {
packages/react-dom/src/client/ReactDOMRoot.js
+1 -12
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {MutableSource, ReactNodeList} from 'shared/ReactTypes';
10 +import type {ReactNodeList} from 'shared/ReactTypes';
11 import type {
12 FiberRoot,
13 TransitionTracingCallbacks,
@@ -47,7 +47,6 @@ export type CreateRootOptions = {
47
48 export type HydrateRootOptions = {
49 // Hydration options
50 - hydratedSources?: Array<MutableSource<any>>,
50 onHydrated?: (suspenseNode: Comment) => void,
51 onDeleted?: (suspenseNode: Comment) => void,
52 // Options for all roots
@@ -77,7 +76,6 @@ import {
76 createHydrationContainer,
77 updateContainer,
78 findHostInstanceWithNoPortals,
80 - registerMutableSourceForHydration,
79 flushSync,
80 isAlreadyRendering,
81 } from 'react-reconciler/src/ReactFiberReconciler';
@@ -298,8 +296,6 @@ export function hydrateRoot(
296 // For now we reuse the whole bag of options since they contain
297 // the hydration callbacks.
298 const hydrationCallbacks = options != null ? options : null;
301 - // TODO: Delete this option
302 - const mutableSources = (options != null && options.hydratedSources) || null;
299
300 let isStrictMode = false;
301 let concurrentUpdatesByDefaultOverride = false;
@@ -344,13 +340,6 @@ export function hydrateRoot(
340 // This can't be a comment node since hydration doesn't work on comment nodes anyway.
341 listenToAllSupportedEvents(container);
342
347 - if (mutableSources) {
348 - for (let i = 0; i < mutableSources.length; i++) {
349 - const mutableSource = mutableSources[i];
350 - registerMutableSourceForHydration(root, mutableSource);
351 - }
352 - }
353 -
343 // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
344 return new ReactDOMHydrationRoot(root);
345 }
packages/react-reconciler/src/ReactFiberBeginWork.js
-16
@@ -11,7 +11,6 @@ import type {
11 ReactProviderType,
12 ReactContext,
13 ReactNodeList,
14 - MutableSource,
14 } from 'shared/ReactTypes';
15 import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
16 import type {Fiber, FiberRoot} from './ReactInternalTypes';
@@ -106,7 +105,6 @@ import {
105 enableTransitionTracing,
106 enableLegacyHidden,
107 enableCPUSuspense,
109 - enableUseMutableSource,
108 enableFloat,
109 enableHostSingletons,
110 enableFormActions,
@@ -261,7 +259,6 @@ import {
259 getWorkInProgressRoot,
260 } from './ReactFiberWorkLoop';
261 import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
264 -import {setWorkInProgressVersion} from './ReactMutableSource';
262 import {pushCacheProvider, CacheContext} from './ReactFiberCacheComponent';
263 import {
264 createCapturedValue,
@@ -1532,19 +1529,6 @@ function updateHostRoot(
1529 } else {
1530 // The outermost shell has not hydrated yet. Start hydrating.
1531 enterHydrationState(workInProgress);
1535 - if (enableUseMutableSource) {
1536 - const mutableSourceEagerHydrationData =
1537 - root.mutableSourceEagerHydrationData;
1538 - if (mutableSourceEagerHydrationData != null) {
1539 - for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
1540 - const mutableSource = ((mutableSourceEagerHydrationData[
1541 - i
1542 - ]: any): MutableSource<any>);
1543 - const version = mutableSourceEagerHydrationData[i + 1];
1544 - setWorkInProgressVersion(mutableSource, version);
1545 - }
1546 - }
1547 - }
1532
1533 const child = mountChildFibers(
1534 workInProgress,
packages/react-reconciler/src/ReactFiberCompleteWork.js
-3
@@ -41,8 +41,6 @@ import {
41 diffInCommitPhase,
42 } from 'shared/ReactFeatureFlags';
43
44 -import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource';
45 -
44 import {now} from './Scheduler';
45
46 import {
@@ -1038,7 +1036,6 @@ function completeWork(
1036 popRootTransition(workInProgress, fiberRoot, renderLanes);
1037 popHostContainer(workInProgress);
1038 popTopLevelLegacyContextObject(workInProgress);
1041 - resetMutableSourceWorkInProgressVersions();
1039 if (fiberRoot.pendingContext) {
1040 fiberRoot.context = fiberRoot.pendingContext;
1041 fiberRoot.pendingContext = null;
packages/react-reconciler/src/ReactFiberHooks.js
-376
@@ -8,9 +8,6 @@
8 */
9
10 import type {
11 - MutableSource,
12 - MutableSourceGetSnapshotFn,
13 - MutableSourceSubscribeFn,
11 ReactContext,
12 StartTransitionOptions,
13 Usable,
@@ -37,7 +34,6 @@ import {
34 enableCache,
35 enableUseRefAccessWarning,
36 enableLazyContextPropagation,
40 - enableUseMutableSource,
37 enableTransitionTracing,
38 enableUseMemoCacheHook,
39 enableUseEffectEventHook,
@@ -74,7 +70,6 @@ import {
70 intersectLanes,
71 isTransitionLane,
72 markRootEntangled,
77 - markRootMutableRead,
73 } from './ReactFiberLane';
74 import {
75 ContinuousEventPriority,
@@ -117,12 +112,6 @@ import {
112 checkIfWorkInProgressReceivedUpdate,
113 } from './ReactFiberBeginWork';
114 import {getIsHydrating} from './ReactFiberHydrationContext';
120 -import {
121 - getWorkInProgressVersion,
122 - markSourceAsDirty,
123 - setWorkInProgressVersion,
124 - warnAboutMultipleRenderersDEV,
125 -} from './ReactMutableSource';
115 import {logStateUpdateScheduled} from './DebugTracing';
116 import {
117 markStateUpdateScheduled,
@@ -1403,301 +1392,6 @@ function rerenderReducer<S, I, A>(
1392 return [newState, dispatch];
1393 }
1394
1406 -type MutableSourceMemoizedState<Source, Snapshot> = {
1407 - refs: {
1408 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1409 - setSnapshot: Snapshot => void,
1410 - },
1411 - source: MutableSource<any>,
1412 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1413 -};
1414 -
1415 -function readFromUnsubscribedMutableSource<Source, Snapshot>(
1416 - root: FiberRoot,
1417 - source: MutableSource<Source>,
1418 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1419 -): Snapshot {
1420 - if (__DEV__) {
1421 - warnAboutMultipleRenderersDEV(source);
1422 - }
1423 -
1424 - const getVersion = source._getVersion;
1425 - const version = getVersion(source._source);
1426 -
1427 - // Is it safe for this component to read from this source during the current render?
1428 - let isSafeToReadFromSource = false;
1429 -
1430 - // Check the version first.
1431 - // If this render has already been started with a specific version,
1432 - // we can use it alone to determine if we can safely read from the source.
1433 - const currentRenderVersion = getWorkInProgressVersion(source);
1434 - if (currentRenderVersion !== null) {
1435 - // It's safe to read if the store hasn't been mutated since the last time
1436 - // we read something.
1437 - isSafeToReadFromSource = currentRenderVersion === version;
1438 - } else {
1439 - // If there's no version, then this is the first time we've read from the
1440 - // source during the current render pass, so we need to do a bit more work.
1441 - // What we need to determine is if there are any hooks that already
1442 - // subscribed to the source, and if so, whether there are any pending
1443 - // mutations that haven't been synchronized yet.
1444 - //
1445 - // If there are no pending mutations, then `root.mutableReadLanes` will be
1446 - // empty, and we know we can safely read.
1447 - //
1448 - // If there *are* pending mutations, we may still be able to safely read
1449 - // if the currently rendering lanes are inclusive of the pending mutation
1450 - // lanes, since that guarantees that the value we're about to read from
1451 - // the source is consistent with the values that we read during the most
1452 - // recent mutation.
1453 - isSafeToReadFromSource = isSubsetOfLanes(
1454 - renderLanes,
1455 - root.mutableReadLanes,
1456 - );
1457 -
1458 - if (isSafeToReadFromSource) {
1459 - // If it's safe to read from this source during the current render,
1460 - // store the version in case other components read from it.
1461 - // A changed version number will let those components know to throw and restart the render.
1462 - setWorkInProgressVersion(source, version);
1463 - }
1464 - }
1465 -
1466 - if (isSafeToReadFromSource) {
1467 - const snapshot = getSnapshot(source._source);
1468 - if (__DEV__) {
1469 - if (typeof snapshot === 'function') {
1470 - console.error(
1471 - 'Mutable source should not return a function as the snapshot value. ' +
1472 - 'Functions may close over mutable values and cause tearing.',
1473 - );
1474 - }
1475 - }
1476 - return snapshot;
1477 - } else {
1478 - // This handles the special case of a mutable source being shared between renderers.
1479 - // In that case, if the source is mutated between the first and second renderer,
1480 - // The second renderer don't know that it needs to reset the WIP version during unwind,
1481 - // (because the hook only marks sources as dirty if it's written to their WIP version).
1482 - // That would cause this tear check to throw again and eventually be visible to the user.
1483 - // We can avoid this infinite loop by explicitly marking the source as dirty.
1484 - //
1485 - // This can lead to tearing in the first renderer when it resumes,
1486 - // but there's nothing we can do about that (short of throwing here and refusing to continue the render).
1487 - markSourceAsDirty(source);
1488 -
1489 - // Intentioally throw an error to force React to retry synchronously. During
1490 - // the synchronous retry, it will block interleaved mutations, so we should
1491 - // get a consistent read. Therefore, the following error should never be
1492 - // visible to the user.
1493 -
1494 - // We expect this error not to be thrown during the synchronous retry,
1495 - // because we blocked interleaved mutations.
1496 - throw new Error(
1497 - 'Cannot read from mutable source during the current render without tearing. This may be a bug in React. Please file an issue.',
1498 - );
1499 - }
1500 -}
1501 -
1502 -function useMutableSource<Source, Snapshot>(
1503 - hook: Hook,
1504 - source: MutableSource<Source>,
1505 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1506 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1507 -): Snapshot {
1508 - if (!enableUseMutableSource) {
1509 - return (undefined: any);
1510 - }
1511 -
1512 - const root = ((getWorkInProgressRoot(): any): FiberRoot);
1513 -
1514 - if (root === null) {
1515 - throw new Error(
1516 - 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
1517 - );
1518 - }
1519 -
1520 - const getVersion = source._getVersion;
1521 - const version = getVersion(source._source);
1522 -
1523 - const dispatcher = ReactCurrentDispatcher.current;
1524 -
1525 - // eslint-disable-next-line prefer-const
1526 - let [currentSnapshot, setSnapshot] = dispatcher.useState(() =>
1527 - readFromUnsubscribedMutableSource(root, source, getSnapshot),
1528 - );
1529 - let snapshot = currentSnapshot;
1530 -
1531 - // Grab a handle to the state hook as well.
1532 - // We use it to clear the pending update queue if we have a new source.
1533 - const stateHook = ((workInProgressHook: any): Hook);
1534 -
1535 - const memoizedState = ((hook.memoizedState: any): MutableSourceMemoizedState<
1536 - Source,
1537 - Snapshot,
1538 - >);
1539 - const refs = memoizedState.refs;
1540 - const prevGetSnapshot = refs.getSnapshot;
1541 - const prevSource = memoizedState.source;
1542 - const prevSubscribe = memoizedState.subscribe;
1543 -
1544 - const fiber = currentlyRenderingFiber;
1545 -
1546 - hook.memoizedState = ({
1547 - refs,
1548 - source,
1549 - subscribe,
1550 - }: MutableSourceMemoizedState<Source, Snapshot>);
1551 -
1552 - // Sync the values needed by our subscription handler after each commit.
1553 - dispatcher.useEffect(() => {
1554 - refs.getSnapshot = getSnapshot;
1555 -
1556 - // Normally the dispatch function for a state hook never changes,
1557 - // but this hook recreates the queue in certain cases to avoid updates from stale sources.
1558 - // handleChange() below needs to reference the dispatch function without re-subscribing,
1559 - // so we use a ref to ensure that it always has the latest version.
1560 - refs.setSnapshot = setSnapshot;
1561 -
1562 - // Check for a possible change between when we last rendered now.
1563 - const maybeNewVersion = getVersion(source._source);
1564 - if (!is(version, maybeNewVersion)) {
1565 - const maybeNewSnapshot = getSnapshot(source._source);
1566 - if (__DEV__) {
1567 - if (typeof maybeNewSnapshot === 'function') {
1568 - console.error(
1569 - 'Mutable source should not return a function as the snapshot value. ' +
1570 - 'Functions may close over mutable values and cause tearing.',
1571 - );
1572 - }
1573 - }
1574 -
1575 - if (!is(snapshot, maybeNewSnapshot)) {
1576 - setSnapshot(maybeNewSnapshot);
1577 -
1578 - const lane = requestUpdateLane(fiber);
1579 - markRootMutableRead(root, lane);
1580 - }
1581 - // If the source mutated between render and now,
1582 - // there may be state updates already scheduled from the old source.
1583 - // Entangle the updates so that they render in the same batch.
1584 - markRootEntangled(root, root.mutableReadLanes);
1585 - }
1586 - }, [getSnapshot, source, subscribe]);
1587 -
1588 - // If we got a new source or subscribe function, re-subscribe in a passive effect.
1589 - dispatcher.useEffect(() => {
1590 - const handleChange = () => {
1591 - const latestGetSnapshot = refs.getSnapshot;
1592 - const latestSetSnapshot = refs.setSnapshot;
1593 -
1594 - try {
1595 - latestSetSnapshot(latestGetSnapshot(source._source));
1596 -
1597 - // Record a pending mutable source update with the same expiration time.
1598 - const lane = requestUpdateLane(fiber);
1599 -
1600 - markRootMutableRead(root, lane);
1601 - } catch (error) {
1602 - // A selector might throw after a source mutation.
1603 - // e.g. it might try to read from a part of the store that no longer exists.
1604 - // In this case we should still schedule an update with React.
1605 - // Worst case the selector will throw again and then an error boundary will handle it.
1606 - latestSetSnapshot(
1607 - (() => {
1608 - throw error;
1609 - }: any),
1610 - );
1611 - }
1612 - };
1613 -
1614 - const unsubscribe = subscribe(source._source, handleChange);
1615 - if (__DEV__) {
1616 - if (typeof unsubscribe !== 'function') {
1617 - console.error(
1618 - 'Mutable source subscribe function must return an unsubscribe function.',
1619 - );
1620 - }
1621 - }
1622 -
1623 - return unsubscribe;
1624 - }, [source, subscribe]);
1625 -
1626 - // If any of the inputs to useMutableSource change, reading is potentially unsafe.
1627 - //
1628 - // If either the source or the subscription have changed we can't can't trust the update queue.
1629 - // Maybe the source changed in a way that the old subscription ignored but the new one depends on.
1630 - //
1631 - // If the getSnapshot function changed, we also shouldn't rely on the update queue.
1632 - // It's possible that the underlying source was mutated between the when the last "change" event fired,
1633 - // and when the current render (with the new getSnapshot function) is processed.
1634 - //
1635 - // In both cases, we need to throw away pending updates (since they are no longer relevant)
1636 - // and treat reading from the source as we do in the mount case.
1637 - if (
1638 - !is(prevGetSnapshot, getSnapshot) ||
1639 - !is(prevSource, source) ||
1640 - !is(prevSubscribe, subscribe)
1641 - ) {
1642 - // Create a new queue and setState method,
1643 - // So if there are interleaved updates, they get pushed to the older queue.
1644 - // When this becomes current, the previous queue and dispatch method will be discarded,
1645 - // including any interleaving updates that occur.
1646 - const newQueue: UpdateQueue<Snapshot, BasicStateAction<Snapshot>> = {
1647 - pending: null,
1648 - lanes: NoLanes,
1649 - dispatch: null,
1650 - lastRenderedReducer: basicStateReducer,
1651 - lastRenderedState: snapshot,
1652 - };
1653 - newQueue.dispatch = setSnapshot = (dispatchSetState.bind(
1654 - null,
1655 - currentlyRenderingFiber,
1656 - newQueue,
1657 - ): any);
1658 - stateHook.queue = newQueue;
1659 - stateHook.baseQueue = null;
1660 - snapshot = readFromUnsubscribedMutableSource(root, source, getSnapshot);
1661 - stateHook.memoizedState = stateHook.baseState = snapshot;
1662 - }
1663 -
1664 - return snapshot;
1665 -}
1666 -
1667 -function mountMutableSource<Source, Snapshot>(
1668 - source: MutableSource<Source>,
1669 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1670 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1671 -): Snapshot {
1672 - if (!enableUseMutableSource) {
1673 - return (undefined: any);
1674 - }
1675 -
1676 - const hook = mountWorkInProgressHook();
1677 - hook.memoizedState = ({
1678 - refs: {
1679 - getSnapshot,
1680 - setSnapshot: (null: any),
1681 - },
1682 - source,
1683 - subscribe,
1684 - }: MutableSourceMemoizedState<Source, Snapshot>);
1685 - return useMutableSource(hook, source, getSnapshot, subscribe);
1686 -}
1687 -
1688 -function updateMutableSource<Source, Snapshot>(
1689 - source: MutableSource<Source>,
1690 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
1691 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
1692 -): Snapshot {
1693 - if (!enableUseMutableSource) {
1694 - return (undefined: any);
1695 - }
1696 -
1697 - const hook = updateWorkInProgressHook();
1698 - return useMutableSource(hook, source, getSnapshot, subscribe);
1699 -}
1700 -
1395 function mountSyncExternalStore<T>(
1396 subscribe: (() => void) => () => void,
1397 getSnapshot: () => T,
@@ -3203,7 +2897,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
2897 useDebugValue: throwInvalidHookError,
2898 useDeferredValue: throwInvalidHookError,
2899 useTransition: throwInvalidHookError,
3206 - useMutableSource: throwInvalidHookError,
2900 useSyncExternalStore: throwInvalidHookError,
2901 useId: throwInvalidHookError,
2902 };
@@ -3241,7 +2934,6 @@ const HooksDispatcherOnMount: Dispatcher = {
2934 useDebugValue: mountDebugValue,
2935 useDeferredValue: mountDeferredValue,
2936 useTransition: mountTransition,
3244 - useMutableSource: mountMutableSource,
2937 useSyncExternalStore: mountSyncExternalStore,
2938 useId: mountId,
2939 };
@@ -3279,7 +2971,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
2971 useDebugValue: updateDebugValue,
2972 useDeferredValue: updateDeferredValue,
2973 useTransition: updateTransition,
3282 - useMutableSource: updateMutableSource,
2974 useSyncExternalStore: updateSyncExternalStore,
2975 useId: updateId,
2976 };
@@ -3317,7 +3008,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
3008 useDebugValue: updateDebugValue,
3009 useDeferredValue: rerenderDeferredValue,
3010 useTransition: rerenderTransition,
3320 - useMutableSource: updateMutableSource,
3011 useSyncExternalStore: updateSyncExternalStore,
3012 useId: updateId,
3013 };
@@ -3478,15 +3168,6 @@ if (__DEV__) {
3168 mountHookTypesDev();
3169 return mountTransition();
3170 },
3481 - useMutableSource<Source, Snapshot>(
3482 - source: MutableSource<Source>,
3483 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3484 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3485 - ): Snapshot {
3486 - currentHookNameInDev = 'useMutableSource';
3487 - mountHookTypesDev();
3488 - return mountMutableSource(source, getSnapshot, subscribe);
3489 - },
3171 useSyncExternalStore<T>(
3172 subscribe: (() => void) => () => void,
3173 getSnapshot: () => T,
@@ -3646,15 +3327,6 @@ if (__DEV__) {
3327 updateHookTypesDev();
3328 return mountTransition();
3329 },
3649 - useMutableSource<Source, Snapshot>(
3650 - source: MutableSource<Source>,
3651 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3652 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3653 - ): Snapshot {
3654 - currentHookNameInDev = 'useMutableSource';
3655 - updateHookTypesDev();
3656 - return mountMutableSource(source, getSnapshot, subscribe);
3657 - },
3330 useSyncExternalStore<T>(
3331 subscribe: (() => void) => () => void,
3332 getSnapshot: () => T,
@@ -3818,15 +3490,6 @@ if (__DEV__) {
3490 updateHookTypesDev();
3491 return updateTransition();
3492 },
3821 - useMutableSource<Source, Snapshot>(
3822 - source: MutableSource<Source>,
3823 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3824 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3825 - ): Snapshot {
3826 - currentHookNameInDev = 'useMutableSource';
3827 - updateHookTypesDev();
3828 - return updateMutableSource(source, getSnapshot, subscribe);
3829 - },
3493 useSyncExternalStore<T>(
3494 subscribe: (() => void) => () => void,
3495 getSnapshot: () => T,
@@ -3989,15 +3652,6 @@ if (__DEV__) {
3652 updateHookTypesDev();
3653 return rerenderTransition();
3654 },
3992 - useMutableSource<Source, Snapshot>(
3993 - source: MutableSource<Source>,
3994 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
3995 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
3996 - ): Snapshot {
3997 - currentHookNameInDev = 'useMutableSource';
3998 - updateHookTypesDev();
3999 - return updateMutableSource(source, getSnapshot, subscribe);
4000 - },
3655 useSyncExternalStore<T>(
3656 subscribe: (() => void) => () => void,
3657 getSnapshot: () => T,
@@ -4174,16 +3828,6 @@ if (__DEV__) {
3828 mountHookTypesDev();
3829 return mountTransition();
3830 },
4177 - useMutableSource<Source, Snapshot>(
4178 - source: MutableSource<Source>,
4179 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
4180 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
4181 - ): Snapshot {
4182 - currentHookNameInDev = 'useMutableSource';
4183 - warnInvalidHookAccess();
4184 - mountHookTypesDev();
4185 - return mountMutableSource(source, getSnapshot, subscribe);
4186 - },
3831 useSyncExternalStore<T>(
3832 subscribe: (() => void) => () => void,
3833 getSnapshot: () => T,
@@ -4371,16 +4015,6 @@ if (__DEV__) {
4015 updateHookTypesDev();
4016 return updateTransition();
4017 },
4374 - useMutableSource<Source, Snapshot>(
4375 - source: MutableSource<Source>,
4376 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
4377 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
4378 - ): Snapshot {
4379 - currentHookNameInDev = 'useMutableSource';
4380 - warnInvalidHookAccess();
4381 - updateHookTypesDev();
4382 - return updateMutableSource(source, getSnapshot, subscribe);
4383 - },
4018 useSyncExternalStore<T>(
4019 subscribe: (() => void) => () => void,
4020 getSnapshot: () => T,
@@ -4568,16 +4202,6 @@ if (__DEV__) {
4202 updateHookTypesDev();
4203 return rerenderTransition();
4204 },
4571 - useMutableSource<Source, Snapshot>(
4572 - source: MutableSource<Source>,
4573 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
4574 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
4575 - ): Snapshot {
4576 - currentHookNameInDev = 'useMutableSource';
4577 - warnInvalidHookAccess();
4578 - updateHookTypesDev();
4579 - return updateMutableSource(source, getSnapshot, subscribe);
4580 - },
4205 useSyncExternalStore<T>(
4206 subscribe: (() => void) => () => void,
4207 getSnapshot: () => T,
packages/react-reconciler/src/ReactFiberLane.js
+2 -7
@@ -299,8 +299,8 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
299 // time it takes to show the final state, which is what they are actually
300 // waiting for.
301 //
302 - // For those exceptions where entanglement is semantically important, like
303 - // useMutableSource, we should ensure that there is no partial work at the
302 + // For those exceptions where entanglement is semantically important,
303 + // we should ensure that there is no partial work at the
304 // time we apply the entanglement.
305 const entangledLanes = root.entangledLanes;
306 if (entangledLanes !== NoLanes) {
@@ -622,10 +622,6 @@ export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
622 root.pingedLanes |= root.suspendedLanes & pingedLanes;
623 }
624
625 -export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
626 - root.mutableReadLanes |= updateLane & root.pendingLanes;
627 -}
628 -
625 export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
626 const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
627
@@ -636,7 +632,6 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
632 root.pingedLanes = NoLanes;
633
634 root.expiredLanes &= remainingLanes;
639 - root.mutableReadLanes &= remainingLanes;
635
636 root.entangledLanes &= remainingLanes;
637
packages/react-reconciler/src/ReactFiberReconciler.js
-1
@@ -97,7 +97,6 @@ import {
97 findHostInstancesForRefresh,
98 } from './ReactFiberHotReloading';
99 import ReactVersion from 'shared/ReactVersion';
100 -export {registerMutableSourceForHydration} from './ReactMutableSource';
100 export {createPortal} from './ReactPortal';
101 export {
102 createComponentSelector,
packages/react-reconciler/src/ReactFiberRoot.js
+1 -6
@@ -17,7 +17,7 @@ import type {RootTag} from './ReactRootTags';
17 import type {Cache} from './ReactFiberCacheComponent';
18 import type {Container} from './ReactFiberConfig';
19
20 -import {noTimeout, supportsHydration} from './ReactFiberConfig';
20 +import {noTimeout} from './ReactFiberConfig';
21 import {createHostRootFiber} from './ReactFiber';
22 import {
23 NoLane,
@@ -72,7 +72,6 @@ function FiberRootNode(
72 this.suspendedLanes = NoLanes;
73 this.pingedLanes = NoLanes;
74 this.expiredLanes = NoLanes;
75 - this.mutableReadLanes = NoLanes;
75 this.finishedLanes = NoLanes;
76 this.errorRecoveryDisabledLanes = NoLanes;
77
@@ -89,10 +88,6 @@ function FiberRootNode(
88 this.pooledCacheLanes = NoLanes;
89 }
90
92 - if (supportsHydration) {
93 - this.mutableSourceEagerHydrationData = null;
94 - }
95 -
91 if (enableSuspenseCallback) {
92 this.hydrationCallbacks = null;
93 }
packages/react-reconciler/src/ReactFiberUnwindWork.js
-3
@@ -14,7 +14,6 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent';
14 import type {Cache} from './ReactFiberCacheComponent';
15 import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';
16
17 -import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource';
17 import {
18 ClassComponent,
19 HostRoot,
@@ -103,7 +102,6 @@ function unwindWork(
102 popRootTransition(workInProgress, root, renderLanes);
103 popHostContainer(workInProgress);
104 popTopLevelLegacyContextObject(workInProgress);
106 - resetMutableSourceWorkInProgressVersions();
105 const flags = workInProgress.flags;
106 if (
107 (flags & ShouldCapture) !== NoFlags &&
@@ -234,7 +232,6 @@ function unwindInterruptedWork(
232 popRootTransition(interruptedWork, root, renderLanes);
233 popHostContainer(interruptedWork);
234 popTopLevelLegacyContextObject(interruptedWork);
237 - resetMutableSourceWorkInProgressVersions();
235 break;
236 }
237 case HostHoistable:
packages/react-reconciler/src/ReactInternalTypes.js
-16
@@ -11,10 +11,6 @@ import type {Source} from 'shared/ReactElementType';
11 import type {
12 RefObject,
13 ReactContext,
14 - MutableSourceSubscribeFn,
15 - MutableSourceGetSnapshotFn,
16 - MutableSourceVersion,
17 - MutableSource,
14 StartTransitionOptions,
15 Wakeable,
16 Usable,
@@ -54,7 +50,6 @@ export type HookType =
50 | 'useDebugValue'
51 | 'useDeferredValue'
52 | 'useTransition'
57 - | 'useMutableSource'
53 | 'useSyncExternalStore'
54 | 'useId'
55 | 'useCacheRefresh'
@@ -237,11 +232,6 @@ type BaseFiberRootProperties = {
232 context: Object | null,
233 pendingContext: Object | null,
234
240 - // Used by useMutableSource hook to avoid tearing during hydration.
241 - mutableSourceEagerHydrationData?: Array<
242 - MutableSource<any> | MutableSourceVersion,
243 - > | null,
244 -
235 // Used to create a linked list that represent all the roots that have
236 // pending work scheduled on them.
237 next: FiberRoot | null,
@@ -257,7 +247,6 @@ type BaseFiberRootProperties = {
247 suspendedLanes: Lanes,
248 pingedLanes: Lanes,
249 expiredLanes: Lanes,
260 - mutableReadLanes: Lanes,
250 errorRecoveryDisabledLanes: Lanes,
251
252 finishedLanes: Lanes,
@@ -410,11 +399,6 @@ export type Dispatcher = {
399 boolean,
400 (callback: () => void, options?: StartTransitionOptions) => void,
401 ],
413 - useMutableSource<TSource, Snapshot>(
414 - source: MutableSource<TSource>,
415 - getSnapshot: MutableSourceGetSnapshotFn<TSource, Snapshot>,
416 - subscribe: MutableSourceSubscribeFn<TSource, Snapshot>,
417 - ): Snapshot,
402 useSyncExternalStore<T>(
403 subscribe: (() => void) => () => void,
404 getSnapshot: () => T,
packages/react-reconciler/src/ReactMutableSource.js deleted
-108
@@ -1,108 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 './ReactFiberConfig';
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
-1886
@@ -1,1886 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 -let waitFor;
23 -let waitForAll;
24 -let assertLog;
25 -let waitForPaint;
26 -
27 -function loadModules() {
28 - jest.resetModules();
29 - jest.useFakeTimers();
30 -
31 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
32 - ReactFeatureFlags.enableProfilerTimer = true;
33 -
34 - React = require('react');
35 - ReactNoop = require('react-noop-renderer');
36 - Scheduler = require('scheduler');
37 - act = require('internal-test-utils').act;
38 -
39 - const InternalTestUtils = require('internal-test-utils');
40 - waitFor = InternalTestUtils.waitFor;
41 - waitForAll = InternalTestUtils.waitForAll;
42 - waitForPaint = InternalTestUtils.waitForPaint;
43 - assertLog = InternalTestUtils.assertLog;
44 -
45 - // Stable entrypoints export with "unstable_" prefix.
46 - createMutableSource =
47 - React.createMutableSource || React.unstable_createMutableSource;
48 - useMutableSource = React.useMutableSource || React.unstable_useMutableSource;
49 -}
50 -
51 -describe('useMutableSource', () => {
52 - const defaultGetSnapshot = source => source.value;
53 - const defaultSubscribe = (source, callback) => source.subscribe(callback);
54 -
55 - function createComplexSource(initialValueA, initialValueB) {
56 - const callbacksA = [];
57 - const callbacksB = [];
58 - let revision = 0;
59 - let valueA = initialValueA;
60 - let valueB = initialValueB;
61 -
62 - const subscribeHelper = (callbacks, callback) => {
63 - if (callbacks.indexOf(callback) < 0) {
64 - callbacks.push(callback);
65 - }
66 - return () => {
67 - const index = callbacks.indexOf(callback);
68 - if (index >= 0) {
69 - callbacks.splice(index, 1);
70 - }
71 - };
72 - };
73 -
74 - return {
75 - subscribeA(callback) {
76 - return subscribeHelper(callbacksA, callback);
77 - },
78 - subscribeB(callback) {
79 - return subscribeHelper(callbacksB, callback);
80 - },
81 -
82 - get listenerCountA() {
83 - return callbacksA.length;
84 - },
85 - get listenerCountB() {
86 - return callbacksB.length;
87 - },
88 -
89 - set valueA(newValue) {
90 - revision++;
91 - valueA = newValue;
92 - callbacksA.forEach(callback => callback());
93 - },
94 - get valueA() {
95 - return valueA;
96 - },
97 -
98 - set valueB(newValue) {
99 - revision++;
100 - valueB = newValue;
101 - callbacksB.forEach(callback => callback());
102 - },
103 - get valueB() {
104 - return valueB;
105 - },
106 -
107 - get version() {
108 - return revision;
109 - },
110 - };
111 - }
112 -
113 - function createSource(initialValue) {
114 - const callbacks = [];
115 - let revision = 0;
116 - let value = initialValue;
117 - return {
118 - subscribe(callback) {
119 - if (callbacks.indexOf(callback) < 0) {
120 - callbacks.push(callback);
121 - }
122 - return () => {
123 - const index = callbacks.indexOf(callback);
124 - if (index >= 0) {
125 - callbacks.splice(index, 1);
126 - }
127 - };
128 - },
129 - get listenerCount() {
130 - return callbacks.length;
131 - },
132 - set value(newValue) {
133 - revision++;
134 - value = newValue;
135 - callbacks.forEach(callback => callback());
136 - },
137 - get value() {
138 - return value;
139 - },
140 - get version() {
141 - return revision;
142 - },
143 - };
144 - }
145 -
146 - function Component({getSnapshot, label, mutableSource, subscribe}) {
147 - const snapshot = useMutableSource(mutableSource, getSnapshot, subscribe);
148 - Scheduler.log(`${label}:${snapshot}`);
149 - return <div>{`${label}:${snapshot}`}</div>;
150 - }
151 -
152 - beforeEach(loadModules);
153 -
154 - // @gate enableUseMutableSource
155 - it('should subscribe to a source and schedule updates when it changes', async () => {
156 - const source = createSource('one');
157 - const mutableSource = createMutableSource(source, param => param.version);
158 -
159 - await act(async () => {
160 - ReactNoop.renderToRootWithID(
161 - <>
162 - <Component
163 - label="a"
164 - getSnapshot={defaultGetSnapshot}
165 - mutableSource={mutableSource}
166 - subscribe={defaultSubscribe}
167 - />
168 - <Component
169 - label="b"
170 - getSnapshot={defaultGetSnapshot}
171 - mutableSource={mutableSource}
172 - subscribe={defaultSubscribe}
173 - />
174 - </>,
175 - 'root',
176 - () => Scheduler.log('Sync effect'),
177 - );
178 - await waitFor(['a:one', 'b:one', 'Sync effect']);
179 -
180 - // Subscriptions should be passive
181 - expect(source.listenerCount).toBe(0);
182 - ReactNoop.flushPassiveEffects();
183 - expect(source.listenerCount).toBe(2);
184 -
185 - // Changing values should schedule an update with React
186 - source.value = 'two';
187 - await waitFor(['a:two', 'b:two']);
188 -
189 - // Unmounting a component should remove its subscription.
190 - ReactNoop.renderToRootWithID(
191 - <>
192 - <Component
193 - label="a"
194 - getSnapshot={defaultGetSnapshot}
195 - mutableSource={mutableSource}
196 - subscribe={defaultSubscribe}
197 - />
198 - </>,
199 - 'root',
200 - () => Scheduler.log('Sync effect'),
201 - );
202 - await waitForAll(['a:two', 'Sync effect']);
203 - ReactNoop.flushPassiveEffects();
204 - expect(source.listenerCount).toBe(1);
205 -
206 - // Unmounting a root should remove the remaining event listeners
207 - ReactNoop.unmountRootWithID('root');
208 - await waitForAll([]);
209 - ReactNoop.flushPassiveEffects();
210 - expect(source.listenerCount).toBe(0);
211 -
212 - // Changes to source should not trigger an updates or warnings.
213 - source.value = 'three';
214 - await waitForAll([]);
215 - });
216 - });
217 -
218 - // @gate enableUseMutableSource
219 - it('should restart work if a new source is mutated during render', async () => {
220 - const source = createSource('one');
221 - const mutableSource = createMutableSource(source, param => param.version);
222 -
223 - await act(async () => {
224 - React.startTransition(() => {
225 - ReactNoop.render(
226 - <>
227 - <Component
228 - label="a"
229 - getSnapshot={defaultGetSnapshot}
230 - mutableSource={mutableSource}
231 - subscribe={defaultSubscribe}
232 - />
233 - <Component
234 - label="b"
235 - getSnapshot={defaultGetSnapshot}
236 - mutableSource={mutableSource}
237 - subscribe={defaultSubscribe}
238 - />
239 - </>,
240 - () => Scheduler.log('Sync effect'),
241 - );
242 - });
243 -
244 - // Do enough work to read from one component
245 - await waitFor(['a:one']);
246 -
247 - // Mutate source before continuing work
248 - source.value = 'two';
249 -
250 - // Render work should restart and the updated value should be used
251 - await waitForAll(['a:two', 'b:two', 'Sync effect']);
252 - });
253 - });
254 -
255 - // @gate enableUseMutableSource
256 - it('should schedule an update if a new source is mutated between render and commit (subscription)', async () => {
257 - const source = createSource('one');
258 - const mutableSource = createMutableSource(source, param => param.version);
259 -
260 - await act(async () => {
261 - ReactNoop.render(
262 - <>
263 - <Component
264 - label="a"
265 - getSnapshot={defaultGetSnapshot}
266 - mutableSource={mutableSource}
267 - subscribe={defaultSubscribe}
268 - />
269 - <Component
270 - label="b"
271 - getSnapshot={defaultGetSnapshot}
272 - mutableSource={mutableSource}
273 - subscribe={defaultSubscribe}
274 - />
275 - </>,
276 - () => Scheduler.log('Sync effect'),
277 - );
278 -
279 - // Finish rendering
280 - await waitFor(['a:one', 'b:one', 'Sync effect']);
281 -
282 - // Mutate source before subscriptions are attached
283 - expect(source.listenerCount).toBe(0);
284 - source.value = 'two';
285 -
286 - // Mutation should be detected, and a new render should be scheduled
287 - await waitForAll(['a:two', 'b:two']);
288 - });
289 - });
290 -
291 - // @gate enableUseMutableSource
292 - it('should unsubscribe and resubscribe if a new source is used', async () => {
293 - const sourceA = createSource('a-one');
294 - const mutableSourceA = createMutableSource(
295 - sourceA,
296 - param => param.versionA,
297 - );
298 -
299 - const sourceB = createSource('b-one');
300 - const mutableSourceB = createMutableSource(
301 - sourceB,
302 - param => param.versionB,
303 - );
304 -
305 - await act(async () => {
306 - ReactNoop.render(
307 - <Component
308 - label="only"
309 - getSnapshot={defaultGetSnapshot}
310 - mutableSource={mutableSourceA}
311 - subscribe={defaultSubscribe}
312 - />,
313 - () => Scheduler.log('Sync effect'),
314 - );
315 - await waitForAll(['only:a-one', 'Sync effect']);
316 - ReactNoop.flushPassiveEffects();
317 - expect(sourceA.listenerCount).toBe(1);
318 -
319 - // Changing values should schedule an update with React
320 - sourceA.value = 'a-two';
321 - await waitForAll(['only:a-two']);
322 -
323 - // If we re-render with a new source, the old one should be unsubscribed.
324 - ReactNoop.render(
325 - <Component
326 - label="only"
327 - getSnapshot={defaultGetSnapshot}
328 - mutableSource={mutableSourceB}
329 - subscribe={defaultSubscribe}
330 - />,
331 - () => Scheduler.log('Sync effect'),
332 - );
333 - await waitForAll(['only:b-one', 'Sync effect']);
334 - ReactNoop.flushPassiveEffects();
335 - expect(sourceA.listenerCount).toBe(0);
336 - expect(sourceB.listenerCount).toBe(1);
337 -
338 - // Changing to original source should not schedule updates with React
339 - sourceA.value = 'a-three';
340 - await waitForAll([]);
341 -
342 - // Changing new source value should schedule an update with React
343 - sourceB.value = 'b-two';
344 - await waitForAll(['only:b-two']);
345 - });
346 - });
347 -
348 - // @gate enableUseMutableSource
349 - it('should unsubscribe and resubscribe if a new subscribe function is provided', async () => {
350 - const source = createSource('a-one');
351 - const mutableSource = createMutableSource(source, param => param.version);
352 -
353 - const unsubscribeA = jest.fn();
354 - const subscribeA = jest.fn(s => {
355 - const unsubscribe = defaultSubscribe(s);
356 - return () => {
357 - unsubscribe();
358 - unsubscribeA();
359 - };
360 - });
361 - const unsubscribeB = jest.fn();
362 - const subscribeB = jest.fn(s => {
363 - const unsubscribe = defaultSubscribe(s);
364 - return () => {
365 - unsubscribe();
366 - unsubscribeB();
367 - };
368 - });
369 -
370 - await act(async () => {
371 - ReactNoop.renderToRootWithID(
372 - <Component
373 - label="only"
374 - getSnapshot={defaultGetSnapshot}
375 - mutableSource={mutableSource}
376 - subscribe={subscribeA}
377 - />,
378 - 'root',
379 - () => Scheduler.log('Sync effect'),
380 - );
381 - await waitForAll(['only:a-one', 'Sync effect']);
382 - ReactNoop.flushPassiveEffects();
383 - expect(source.listenerCount).toBe(1);
384 - expect(subscribeA).toHaveBeenCalledTimes(1);
385 -
386 - // If we re-render with a new subscription function,
387 - // the old unsubscribe function should be called.
388 - ReactNoop.renderToRootWithID(
389 - <Component
390 - label="only"
391 - getSnapshot={defaultGetSnapshot}
392 - mutableSource={mutableSource}
393 - subscribe={subscribeB}
394 - />,
395 - 'root',
396 - () => Scheduler.log('Sync effect'),
397 - );
398 - await waitForAll(['only:a-one', 'Sync effect']);
399 - ReactNoop.flushPassiveEffects();
400 - expect(source.listenerCount).toBe(1);
401 - expect(unsubscribeA).toHaveBeenCalledTimes(1);
402 - expect(subscribeB).toHaveBeenCalledTimes(1);
403 -
404 - // Unmounting should call the newer unsubscribe.
405 - ReactNoop.unmountRootWithID('root');
406 - await waitForAll([]);
407 - ReactNoop.flushPassiveEffects();
408 - expect(source.listenerCount).toBe(0);
409 - expect(unsubscribeB).toHaveBeenCalledTimes(1);
410 - });
411 - });
412 -
413 - // @gate enableUseMutableSource
414 - it('should re-use previously read snapshot value when reading is unsafe', async () => {
415 - const source = createSource('one');
416 - const mutableSource = createMutableSource(source, param => param.version);
417 -
418 - await act(async () => {
419 - ReactNoop.render(
420 - <>
421 - <Component
422 - label="a"
423 - getSnapshot={defaultGetSnapshot}
424 - mutableSource={mutableSource}
425 - subscribe={defaultSubscribe}
426 - />
427 - <Component
428 - label="b"
429 - getSnapshot={defaultGetSnapshot}
430 - mutableSource={mutableSource}
431 - subscribe={defaultSubscribe}
432 - />
433 - </>,
434 - () => Scheduler.log('Sync effect'),
435 - );
436 - await waitForAll(['a:one', 'b:one', 'Sync effect']);
437 -
438 - // Changing values should schedule an update with React.
439 - // Start working on this update but don't finish it.
440 - React.startTransition(() => {
441 - source.value = 'two';
442 - });
443 - await waitFor(['a:two']);
444 -
445 - // Re-renders that occur before the update is processed
446 - // should reuse snapshot so long as the config has not changed
447 - ReactNoop.flushSync(() => {
448 - ReactNoop.render(
449 - <>
450 - <Component
451 - label="a"
452 - getSnapshot={defaultGetSnapshot}
453 - mutableSource={mutableSource}
454 - subscribe={defaultSubscribe}
455 - />
456 - <Component
457 - label="b"
458 - getSnapshot={defaultGetSnapshot}
459 - mutableSource={mutableSource}
460 - subscribe={defaultSubscribe}
461 - />
462 - </>,
463 - () => Scheduler.log('Sync effect'),
464 - );
465 - });
466 - assertLog(['a:one', 'b:one', 'Sync effect']);
467 -
468 - await waitForAll(['a:two', 'b:two']);
469 - });
470 - });
471 -
472 - // @gate enableUseMutableSource
473 - it('should read from source on newly mounted subtree if no pending updates are scheduled for source', async () => {
474 - const source = createSource('one');
475 - const mutableSource = createMutableSource(source, param => param.version);
476 -
477 - await act(async () => {
478 - ReactNoop.render(
479 - <>
480 - <Component
481 - label="a"
482 - getSnapshot={defaultGetSnapshot}
483 - mutableSource={mutableSource}
484 - subscribe={defaultSubscribe}
485 - />
486 - </>,
487 - () => Scheduler.log('Sync effect'),
488 - );
489 - await waitForAll(['a:one', 'Sync effect']);
490 -
491 - ReactNoop.render(
492 - <>
493 - <Component
494 - label="a"
495 - getSnapshot={defaultGetSnapshot}
496 - mutableSource={mutableSource}
497 - subscribe={defaultSubscribe}
498 - />
499 - <Component
500 - label="b"
501 - getSnapshot={defaultGetSnapshot}
502 - mutableSource={mutableSource}
503 - subscribe={defaultSubscribe}
504 - />
505 - </>,
506 - () => Scheduler.log('Sync effect'),
507 - );
508 - await waitForAll(['a:one', 'b:one', 'Sync effect']);
509 - });
510 - });
511 -
512 - // @gate enableUseMutableSource
513 - it('should throw and restart render if source and snapshot are unavailable during an update', async () => {
514 - const source = createSource('one');
515 - const mutableSource = createMutableSource(source, param => param.version);
516 -
517 - await act(async () => {
518 - ReactNoop.render(
519 - <>
520 - <Component
521 - label="a"
522 - getSnapshot={defaultGetSnapshot}
523 - mutableSource={mutableSource}
524 - subscribe={defaultSubscribe}
525 - />
526 - <Component
527 - label="b"
528 - getSnapshot={defaultGetSnapshot}
529 - mutableSource={mutableSource}
530 - subscribe={defaultSubscribe}
531 - />
532 - </>,
533 - () => Scheduler.log('Sync effect'),
534 - );
535 - await waitForAll(['a:one', 'b:one', 'Sync effect']);
536 - ReactNoop.flushPassiveEffects();
537 -
538 - // Changing values should schedule an update with React.
539 - ReactNoop.idleUpdates(() => {
540 - source.value = 'two';
541 - });
542 -
543 - // Start working on this update but don't finish it.
544 - await waitFor(['a:two']);
545 -
546 - const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
547 -
548 - // Force a higher priority render with a new config.
549 - // This should signal that the snapshot is not safe and trigger a full re-render.
550 - ReactNoop.flushSync(() => {
551 - ReactNoop.render(
552 - <>
553 - <Component
554 - label="a"
555 - getSnapshot={newGetSnapshot}
556 - mutableSource={mutableSource}
557 - subscribe={defaultSubscribe}
558 - />
559 - <Component
560 - label="b"
561 - getSnapshot={newGetSnapshot}
562 - mutableSource={mutableSource}
563 - subscribe={defaultSubscribe}
564 - />
565 - </>,
566 - () => Scheduler.log('Sync effect'),
567 - );
568 - });
569 - assertLog(['a:new:two', 'b:new:two', 'Sync effect']);
570 - });
571 - });
572 -
573 - // @gate enableUseMutableSource
574 - it('should throw and restart render if source and snapshot are unavailable during a sync update', async () => {
575 - const source = createSource('one');
576 - const mutableSource = createMutableSource(source, param => param.version);
577 -
578 - await act(async () => {
579 - ReactNoop.render(
580 - <>
581 - <Component
582 - label="a"
583 - getSnapshot={defaultGetSnapshot}
584 - mutableSource={mutableSource}
585 - subscribe={defaultSubscribe}
586 - />
587 - <Component
588 - label="b"
589 - getSnapshot={defaultGetSnapshot}
590 - mutableSource={mutableSource}
591 - subscribe={defaultSubscribe}
592 - />
593 - </>,
594 - () => Scheduler.log('Sync effect'),
595 - );
596 - await waitForAll(['a:one', 'b:one', 'Sync effect']);
597 - ReactNoop.flushPassiveEffects();
598 -
599 - // Changing values should schedule an update with React.
600 - ReactNoop.idleUpdates(() => {
601 - source.value = 'two';
602 - });
603 -
604 - // Start working on this update but don't finish it.
605 - await waitFor(['a:two']);
606 -
607 - const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
608 -
609 - // Force a higher priority render with a new config.
610 - // This should signal that the snapshot is not safe and trigger a full re-render.
611 - ReactNoop.flushSync(() => {
612 - ReactNoop.render(
613 - <>
614 - <Component
615 - label="a"
616 - getSnapshot={newGetSnapshot}
617 - mutableSource={mutableSource}
618 - subscribe={defaultSubscribe}
619 - />
620 - <Component
621 - label="b"
622 - getSnapshot={newGetSnapshot}
623 - mutableSource={mutableSource}
624 - subscribe={defaultSubscribe}
625 - />
626 - </>,
627 - () => Scheduler.log('Sync effect'),
628 - );
629 - });
630 - assertLog(['a:new:two', 'b:new:two', 'Sync effect']);
631 - });
632 - });
633 -
634 - // @gate enableUseMutableSource
635 - it('should only update components whose subscriptions fire', async () => {
636 - const source = createComplexSource('a:one', 'b:one');
637 - const mutableSource = createMutableSource(source, param => param.version);
638 -
639 - // Subscribe to part of the store.
640 - const getSnapshotA = s => s.valueA;
641 - const subscribeA = (s, callback) => s.subscribeA(callback);
642 - const getSnapshotB = s => s.valueB;
643 - const subscribeB = (s, callback) => s.subscribeB(callback);
644 -
645 - await act(async () => {
646 - ReactNoop.render(
647 - <>
648 - <Component
649 - label="a"
650 - getSnapshot={getSnapshotA}
651 - mutableSource={mutableSource}
652 - subscribe={subscribeA}
653 - />
654 - <Component
655 - label="b"
656 - getSnapshot={getSnapshotB}
657 - mutableSource={mutableSource}
658 - subscribe={subscribeB}
659 - />
660 - </>,
661 - () => Scheduler.log('Sync effect'),
662 - );
663 - await waitForAll(['a:a:one', 'b:b:one', 'Sync effect']);
664 -
665 - // Changes to part of the store (e.g. A) should not render other parts.
666 - source.valueA = 'a:two';
667 - await waitForAll(['a:a:two']);
668 - source.valueB = 'b:two';
669 - await waitForAll(['b:b:two']);
670 - });
671 - });
672 -
673 - // @gate enableUseMutableSource
674 - it('should detect tearing in part of the store not yet subscribed to', async () => {
675 - const source = createComplexSource('a:one', 'b:one');
676 - const mutableSource = createMutableSource(source, param => param.version);
677 -
678 - // Subscribe to part of the store.
679 - const getSnapshotA = s => s.valueA;
680 - const subscribeA = (s, callback) => s.subscribeA(callback);
681 - const getSnapshotB = s => s.valueB;
682 - const subscribeB = (s, callback) => s.subscribeB(callback);
683 -
684 - await act(async () => {
685 - ReactNoop.render(
686 - <>
687 - <Component
688 - label="a"
689 - getSnapshot={getSnapshotA}
690 - mutableSource={mutableSource}
691 - subscribe={subscribeA}
692 - />
693 - </>,
694 - () => Scheduler.log('Sync effect'),
695 - );
696 - await waitForAll(['a:a:one', 'Sync effect']);
697 -
698 - // Because the store has not changed yet, there are no pending updates,
699 - // so it is considered safe to read from when we start this render.
700 - React.startTransition(() => {
701 - ReactNoop.render(
702 - <>
703 - <Component
704 - label="a"
705 - getSnapshot={getSnapshotA}
706 - mutableSource={mutableSource}
707 - subscribe={subscribeA}
708 - />
709 - <Component
710 - label="b"
711 - getSnapshot={getSnapshotB}
712 - mutableSource={mutableSource}
713 - subscribe={subscribeB}
714 - />
715 - <Component
716 - label="c"
717 - getSnapshot={getSnapshotB}
718 - mutableSource={mutableSource}
719 - subscribe={subscribeB}
720 - />
721 - </>,
722 - () => Scheduler.log('Sync effect'),
723 - );
724 - });
725 -
726 - await waitFor(['a:a:one', 'b:b:one']);
727 -
728 - // Mutating the source should trigger a tear detection on the next read,
729 - // which should throw and re-render the entire tree.
730 - source.valueB = 'b:two';
731 -
732 - await waitForAll(['a:a:one', 'b:b:two', 'c:b:two', 'Sync effect']);
733 - });
734 - });
735 -
736 - // @gate enableUseMutableSource
737 - it('does not schedule an update for subscriptions that fire with an unchanged snapshot', async () => {
738 - const MockComponent = jest.fn(Component);
739 -
740 - const source = createSource('one');
741 - const mutableSource = createMutableSource(source, param => param.version);
742 -
743 - await act(async () => {
744 - ReactNoop.render(
745 - <MockComponent
746 - label="only"
747 - getSnapshot={defaultGetSnapshot}
748 - mutableSource={mutableSource}
749 - subscribe={defaultSubscribe}
750 - />,
751 - () => Scheduler.log('Sync effect'),
752 - );
753 - await waitFor(['only:one', 'Sync effect']);
754 - ReactNoop.flushPassiveEffects();
755 - expect(source.listenerCount).toBe(1);
756 -
757 - // Notify subscribe function but don't change the value
758 - source.value = 'one';
759 - await waitForAll([]);
760 - });
761 - });
762 -
763 - // @gate enableUseMutableSource
764 - it('should throw and restart if getSnapshot changes between scheduled update and re-render', async () => {
765 - const source = createSource('one');
766 - const mutableSource = createMutableSource(source, param => param.version);
767 -
768 - const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
769 -
770 - let updateGetSnapshot;
771 -
772 - function WrapperWithState() {
773 - const tuple = React.useState(() => defaultGetSnapshot);
774 - updateGetSnapshot = tuple[1];
775 - return (
776 - <Component
777 - label="only"
778 - getSnapshot={tuple[0]}
779 - mutableSource={mutableSource}
780 - subscribe={defaultSubscribe}
781 - />
782 - );
783 - }
784 -
785 - await act(async () => {
786 - ReactNoop.render(<WrapperWithState />, () =>
787 - Scheduler.log('Sync effect'),
788 - );
789 - await waitForAll(['only:one', 'Sync effect']);
790 - ReactNoop.flushPassiveEffects();
791 -
792 - // Change the source (and schedule an update).
793 - source.value = 'two';
794 -
795 - // Schedule a higher priority update that changes getSnapshot.
796 - ReactNoop.flushSync(() => {
797 - updateGetSnapshot(() => newGetSnapshot);
798 - });
799 -
800 - assertLog(['only:new:two']);
801 - });
802 - });
803 -
804 - // @gate enableUseMutableSource
805 - it('should recover from a mutation during yield when other work is scheduled', async () => {
806 - const source = createSource('one');
807 - const mutableSource = createMutableSource(source, param => param.version);
808 -
809 - await act(async () => {
810 - // Start a render that uses the mutable source.
811 - React.startTransition(() => {
812 - ReactNoop.render(
813 - <>
814 - <Component
815 - label="a"
816 - getSnapshot={defaultGetSnapshot}
817 - mutableSource={mutableSource}
818 - subscribe={defaultSubscribe}
819 - />
820 - <Component
821 - label="b"
822 - getSnapshot={defaultGetSnapshot}
823 - mutableSource={mutableSource}
824 - subscribe={defaultSubscribe}
825 - />
826 - </>,
827 - );
828 - });
829 -
830 - await waitFor(['a:one']);
831 -
832 - // Mutate source
833 - source.value = 'two';
834 -
835 - // Now render something different.
836 - ReactNoop.render(<div />);
837 - await waitForAll([]);
838 - });
839 - });
840 -
841 - // @gate enableUseMutableSource
842 - it('should not throw if the new getSnapshot returns the same snapshot value', async () => {
843 - const source = createSource('one');
844 - const mutableSource = createMutableSource(source, param => param.version);
845 -
846 - const onRenderA = jest.fn();
847 - const onRenderB = jest.fn();
848 -
849 - let updateGetSnapshot;
850 -
851 - function WrapperWithState() {
852 - const tuple = React.useState(() => defaultGetSnapshot);
853 - updateGetSnapshot = tuple[1];
854 - return (
855 - <Component
856 - label="b"
857 - getSnapshot={tuple[0]}
858 - mutableSource={mutableSource}
859 - subscribe={defaultSubscribe}
860 - />
861 - );
862 - }
863 -
864 - await act(async () => {
865 - ReactNoop.render(
866 - <>
867 - <React.Profiler id="a" onRender={onRenderA}>
868 - <Component
869 - label="a"
870 - getSnapshot={defaultGetSnapshot}
871 - mutableSource={mutableSource}
872 - subscribe={defaultSubscribe}
873 - />
874 - </React.Profiler>
875 - <React.Profiler id="b" onRender={onRenderB}>
876 - <WrapperWithState />
877 - </React.Profiler>
878 - </>,
879 - () => Scheduler.log('Sync effect'),
880 - );
881 - await waitForAll(['a:one', 'b:one', 'Sync effect']);
882 - ReactNoop.flushPassiveEffects();
883 - expect(onRenderA).toHaveBeenCalledTimes(1);
884 - expect(onRenderB).toHaveBeenCalledTimes(1);
885 -
886 - // If B's getSnapshot function updates, but the snapshot it returns is the same,
887 - // only B should re-render (to update its state).
888 - updateGetSnapshot(() => s => defaultGetSnapshot(s));
889 - await waitForAll(['b:one']);
890 - ReactNoop.flushPassiveEffects();
891 - expect(onRenderA).toHaveBeenCalledTimes(1);
892 - expect(onRenderB).toHaveBeenCalledTimes(2);
893 - });
894 - });
895 -
896 - // @gate enableUseMutableSource
897 - it('should not throw if getSnapshot changes but the source can be safely read from anyway', async () => {
898 - const source = createSource('one');
899 - const mutableSource = createMutableSource(source, param => param.version);
900 -
901 - const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
902 -
903 - let updateGetSnapshot;
904 -
905 - function WrapperWithState() {
906 - const tuple = React.useState(() => defaultGetSnapshot);
907 - updateGetSnapshot = tuple[1];
908 - return (
909 - <Component
910 - label="only"
911 - getSnapshot={tuple[0]}
912 - mutableSource={mutableSource}
913 - subscribe={defaultSubscribe}
914 - />
915 - );
916 - }
917 -
918 - await act(async () => {
919 - ReactNoop.render(<WrapperWithState />, () =>
920 - Scheduler.log('Sync effect'),
921 - );
922 - await waitForAll(['only:one', 'Sync effect']);
923 - ReactNoop.flushPassiveEffects();
924 -
925 - // Change the source (and schedule an update)
926 - // but also change the snapshot function too.
927 - ReactNoop.batchedUpdates(() => {
928 - source.value = 'two';
929 - updateGetSnapshot(() => newGetSnapshot);
930 - });
931 -
932 - await waitForAll(['only:new:two']);
933 - });
934 - });
935 -
936 - // @gate enableUseMutableSource
937 - it('should still schedule an update if an eager selector throws after a mutation', async () => {
938 - const source = createSource({
939 - friends: [
940 - {id: 1, name: 'Foo'},
941 - {id: 2, name: 'Bar'},
942 - ],
943 - });
944 - const mutableSource = createMutableSource(source, param => param.version);
945 -
946 - function FriendsList() {
947 - const getSnapshot = React.useCallback(
948 - ({value}) => Array.from(value.friends),
949 - [],
950 - );
951 - const friends = useMutableSource(
952 - mutableSource,
953 - getSnapshot,
954 - defaultSubscribe,
955 - );
956 - return (
957 - <ul>
958 - {friends.map(friend => (
959 - <Friend key={friend.id} id={friend.id} />
960 - ))}
961 - </ul>
962 - );
963 - }
964 -
965 - function Friend({id}) {
966 - const getSnapshot = React.useCallback(
967 - ({value}) => {
968 - // This selector is intentionally written in a way that will throw
969 - // if no matching friend exists in the store.
970 - return value.friends.find(friend => friend.id === id).name;
971 - },
972 - [id],
973 - );
974 - const name = useMutableSource(
975 - mutableSource,
976 - getSnapshot,
977 - defaultSubscribe,
978 - );
979 - Scheduler.log(`${id}:${name}`);
980 - return <li>{name}</li>;
981 - }
982 -
983 - await act(async () => {
984 - ReactNoop.render(<FriendsList />, () => Scheduler.log('Sync effect'));
985 - await waitForAll(['1:Foo', '2:Bar', 'Sync effect']);
986 -
987 - // This mutation will cause the "Bar" component to throw,
988 - // since its value will no longer be a part of the store.
989 - // Mutable source should still schedule an update though,
990 - // which should unmount "Bar" and mount "Baz".
991 - source.value = {
992 - friends: [
993 - {id: 1, name: 'Foo'},
994 - {id: 3, name: 'Baz'},
995 - ],
996 - };
997 - await waitForAll(['1:Foo', '3:Baz']);
998 - });
999 - });
1000 -
1001 - // @gate enableUseMutableSource
1002 - it('should not warn about updates that fire between unmount and passive unsubscribe', async () => {
1003 - const source = createSource('one');
1004 - const mutableSource = createMutableSource(source, param => param.version);
1005 -
1006 - function Wrapper() {
1007 - React.useLayoutEffect(() => () => {
1008 - Scheduler.log('layout unmount');
1009 - });
1010 - return (
1011 - <Component
1012 - label="only"
1013 - getSnapshot={defaultGetSnapshot}
1014 - mutableSource={mutableSource}
1015 - subscribe={defaultSubscribe}
1016 - />
1017 - );
1018 - }
1019 -
1020 - await act(async () => {
1021 - ReactNoop.renderToRootWithID(<Wrapper />, 'root', () =>
1022 - Scheduler.log('Sync effect'),
1023 - );
1024 - await waitForAll(['only:one', 'Sync effect']);
1025 - ReactNoop.flushPassiveEffects();
1026 -
1027 - // Unmounting a root should remove the remaining event listeners in a passive effect
1028 - ReactNoop.unmountRootWithID('root');
1029 - await waitFor(['layout unmount']);
1030 -
1031 - // Changes to source should not cause a warning,
1032 - // even though the unsubscribe hasn't run yet (since it's a pending passive effect).
1033 - source.value = 'two';
1034 - await waitForAll([]);
1035 - });
1036 - });
1037 -
1038 - // @gate enableUseMutableSource
1039 - it('should support inline selectors and updates that are processed after selector change', async () => {
1040 - const source = createSource({
1041 - a: 'initial',
1042 - b: 'initial',
1043 - });
1044 - const mutableSource = createMutableSource(source, param => param.version);
1045 -
1046 - const getSnapshotA = () => source.value.a;
1047 - const getSnapshotB = () => source.value.b;
1048 -
1049 - function mutateB(newB) {
1050 - source.value = {
1051 - ...source.value,
1052 - b: newB,
1053 - };
1054 - }
1055 -
1056 - function App({getSnapshot}) {
1057 - const state = useMutableSource(
1058 - mutableSource,
1059 - getSnapshot,
1060 - defaultSubscribe,
1061 - );
1062 - return state;
1063 - }
1064 -
1065 - const root = ReactNoop.createRoot();
1066 - await act(() => {
1067 - root.render(<App getSnapshot={getSnapshotA} />);
1068 - });
1069 - expect(root).toMatchRenderedOutput('initial');
1070 -
1071 - await act(() => {
1072 - mutateB('Updated B');
1073 - root.render(<App getSnapshot={getSnapshotB} />);
1074 - });
1075 - expect(root).toMatchRenderedOutput('Updated B');
1076 -
1077 - await act(() => {
1078 - mutateB('Another update');
1079 - });
1080 - expect(root).toMatchRenderedOutput('Another update');
1081 - });
1082 -
1083 - // @gate enableUseMutableSource
1084 - it('should clear the update queue when getSnapshot changes with pending lower priority updates', async () => {
1085 - const source = createSource({
1086 - a: 'initial',
1087 - b: 'initial',
1088 - });
1089 - const mutableSource = createMutableSource(source, param => param.version);
1090 -
1091 - const getSnapshotA = () => source.value.a;
1092 - const getSnapshotB = () => source.value.b;
1093 -
1094 - function mutateA(newA) {
1095 - source.value = {
1096 - ...source.value,
1097 - a: newA,
1098 - };
1099 - }
1100 -
1101 - function mutateB(newB) {
1102 - source.value = {
1103 - ...source.value,
1104 - b: newB,
1105 - };
1106 - }
1107 -
1108 - function App({toggle}) {
1109 - const state = useMutableSource(
1110 - mutableSource,
1111 - toggle ? getSnapshotB : getSnapshotA,
1112 - defaultSubscribe,
1113 - );
1114 - const result = (toggle ? 'B: ' : 'A: ') + state;
1115 - return result;
1116 - }
1117 -
1118 - const root = ReactNoop.createRoot();
1119 - await act(() => {
1120 - root.render(<App toggle={false} />);
1121 - });
1122 - expect(root).toMatchRenderedOutput('A: initial');
1123 -
1124 - await act(() => {
1125 - ReactNoop.discreteUpdates(() => {
1126 - // Update both A and B to the same value
1127 - mutateA('Update');
1128 - mutateB('Update');
1129 - // Toggle to B in the same batch
1130 - root.render(<App toggle={true} />);
1131 - });
1132 - // Mutate A at lower priority. This should never be rendered, because
1133 - // by the time we get to the lower priority, we've already switched
1134 - // to B.
1135 - mutateA('OOPS! This mutation should be ignored');
1136 - });
1137 - expect(root).toMatchRenderedOutput('B: Update');
1138 - });
1139 -
1140 - // @gate enableUseMutableSource
1141 - it('should clear the update queue when source changes with pending lower priority updates', async () => {
1142 - const sourceA = createSource('initial');
1143 - const sourceB = createSource('initial');
1144 - const mutableSourceA = createMutableSource(
1145 - sourceA,
1146 - param => param.versionA,
1147 - );
1148 - const mutableSourceB = createMutableSource(
1149 - sourceB,
1150 - param => param.versionB,
1151 - );
1152 -
1153 - function App({toggle}) {
1154 - const state = useMutableSource(
1155 - toggle ? mutableSourceB : mutableSourceA,
1156 - defaultGetSnapshot,
1157 - defaultSubscribe,
1158 - );
1159 - const result = (toggle ? 'B: ' : 'A: ') + state;
1160 - return result;
1161 - }
1162 -
1163 - const root = ReactNoop.createRoot();
1164 - await act(() => {
1165 - root.render(<App toggle={false} />);
1166 - });
1167 - expect(root).toMatchRenderedOutput('A: initial');
1168 -
1169 - await act(() => {
1170 - ReactNoop.discreteUpdates(() => {
1171 - // Update both A and B to the same value
1172 - sourceA.value = 'Update';
1173 - sourceB.value = 'Update';
1174 - // Toggle to B in the same batch
1175 - root.render(<App toggle={true} />);
1176 - });
1177 - // Mutate A at lower priority. This should never be rendered, because
1178 - // by the time we get to the lower priority, we've already switched
1179 - // to B.
1180 - sourceA.value = 'OOPS! This mutation should be ignored';
1181 - });
1182 - expect(root).toMatchRenderedOutput('B: Update');
1183 - });
1184 -
1185 - // @gate enableUseMutableSource
1186 - it('should always treat reading as potentially unsafe when getSnapshot changes between renders', async () => {
1187 - const source = createSource({
1188 - a: 'foo',
1189 - b: 'bar',
1190 - });
1191 - const mutableSource = createMutableSource(source, param => param.version);
1192 -
1193 - const getSnapshotA = () => source.value.a;
1194 - const getSnapshotB = () => source.value.b;
1195 -
1196 - function mutateA(newA) {
1197 - source.value = {
1198 - ...source.value,
1199 - a: newA,
1200 - };
1201 - }
1202 -
1203 - function App({getSnapshotFirst, getSnapshotSecond}) {
1204 - const first = useMutableSource(
1205 - mutableSource,
1206 - getSnapshotFirst,
1207 - defaultSubscribe,
1208 - );
1209 - const second = useMutableSource(
1210 - mutableSource,
1211 - getSnapshotSecond,
1212 - defaultSubscribe,
1213 - );
1214 -
1215 - let result = `x: ${first}, y: ${second}`;
1216 -
1217 - if (getSnapshotFirst === getSnapshotSecond) {
1218 - // When both getSnapshot functions are equal,
1219 - // the two values must be consistent.
1220 - if (first !== second) {
1221 - result = 'Oops, tearing!';
1222 - }
1223 - }
1224 -
1225 - React.useEffect(() => {
1226 - Scheduler.log(result);
1227 - }, [result]);
1228 -
1229 - return result;
1230 - }
1231 -
1232 - const root = ReactNoop.createRoot();
1233 - await act(() => {
1234 - root.render(
1235 - <App
1236 - getSnapshotFirst={getSnapshotA}
1237 - getSnapshotSecond={getSnapshotB}
1238 - />,
1239 - );
1240 - });
1241 - // x and y start out reading from different parts of the store.
1242 - assertLog(['x: foo, y: bar']);
1243 -
1244 - await act(() => {
1245 - ReactNoop.discreteUpdates(() => {
1246 - // At high priority, toggle y so that it reads from A instead of B.
1247 - // Simultaneously, mutate A.
1248 - mutateA('baz');
1249 - root.render(
1250 - <App
1251 - getSnapshotFirst={getSnapshotA}
1252 - getSnapshotSecond={getSnapshotA}
1253 - />,
1254 - );
1255 -
1256 - // If this update were processed before the next mutation,
1257 - // it would be expected to yield "baz" and "baz".
1258 - });
1259 -
1260 - // At lower priority, mutate A again.
1261 - // This happens to match the initial value of B.
1262 - mutateA('bar');
1263 -
1264 - // When this update is processed,
1265 - // it is expected to yield "bar" and "bar".
1266 - });
1267 -
1268 - // Check that we didn't commit any inconsistent states.
1269 - // The actual sequence of work will be:
1270 - // 1. React renders the high-pri update, sees a new getSnapshot, detects the source has been further mutated, and throws
1271 - // 2. React re-renders with all pending updates, including the second mutation, and renders "bar" and "bar".
1272 - assertLog(['x: bar, y: bar']);
1273 - });
1274 -
1275 - // @gate enableUseMutableSource
1276 - it('getSnapshot changes and then source is mutated in between paint and passive effect phase', async () => {
1277 - const source = createSource({
1278 - a: 'foo',
1279 - b: 'bar',
1280 - });
1281 - const mutableSource = createMutableSource(source, param => param.version);
1282 -
1283 - function mutateB(newB) {
1284 - source.value = {
1285 - ...source.value,
1286 - b: newB,
1287 - };
1288 - }
1289 -
1290 - const getSnapshotA = () => source.value.a;
1291 - const getSnapshotB = () => source.value.b;
1292 -
1293 - function App({getSnapshot}) {
1294 - const value = useMutableSource(
1295 - mutableSource,
1296 - getSnapshot,
1297 - defaultSubscribe,
1298 - );
1299 -
1300 - Scheduler.log('Render: ' + value);
1301 - React.useEffect(() => {
1302 - Scheduler.log('Commit: ' + value);
1303 - }, [value]);
1304 -
1305 - return value;
1306 - }
1307 -
1308 - const root = ReactNoop.createRoot();
1309 - await act(() => {
1310 - root.render(<App getSnapshot={getSnapshotA} />);
1311 - });
1312 - assertLog(['Render: foo', 'Commit: foo']);
1313 -
1314 - await act(async () => {
1315 - // Switch getSnapshot to read from B instead
1316 - root.render(<App getSnapshot={getSnapshotB} />);
1317 - // Render and finish the tree, but yield right after paint, before
1318 - // the passive effects have fired.
1319 - await waitForPaint(['Render: bar']);
1320 - // Then mutate B.
1321 - mutateB('baz');
1322 - });
1323 - assertLog([
1324 - // Fires the effect from the previous render
1325 - 'Commit: bar',
1326 - // During that effect, it should detect that the snapshot has changed
1327 - // and re-render.
1328 - 'Render: baz',
1329 - 'Commit: baz',
1330 - ]);
1331 - expect(root).toMatchRenderedOutput('baz');
1332 - });
1333 -
1334 - // @gate enableUseMutableSource
1335 - it('getSnapshot changes and then source is mutated in between paint and passive effect phase, case 2', async () => {
1336 - const source = createSource({
1337 - a: 'a0',
1338 - b: 'b0',
1339 - });
1340 - const mutableSource = createMutableSource(source, param => param.version);
1341 -
1342 - const getSnapshotA = () => source.value.a;
1343 - const getSnapshotB = () => source.value.b;
1344 -
1345 - function mutateA(newA) {
1346 - source.value = {
1347 - ...source.value,
1348 - a: newA,
1349 - };
1350 - }
1351 -
1352 - function App({getSnapshotFirst, getSnapshotSecond}) {
1353 - const first = useMutableSource(
1354 - mutableSource,
1355 - getSnapshotFirst,
1356 - defaultSubscribe,
1357 - );
1358 - const second = useMutableSource(
1359 - mutableSource,
1360 - getSnapshotSecond,
1361 - defaultSubscribe,
1362 - );
1363 -
1364 - return `first: ${first}, second: ${second}`;
1365 - }
1366 -
1367 - const root = ReactNoop.createRoot();
1368 - await act(() => {
1369 - root.render(
1370 - <App
1371 - getSnapshotFirst={getSnapshotA}
1372 - getSnapshotSecond={getSnapshotB}
1373 - />,
1374 - );
1375 - });
1376 - expect(root.getChildrenAsJSX()).toEqual('first: a0, second: b0');
1377 -
1378 - await act(async () => {
1379 - // Switch the second getSnapshot to also read from A
1380 - root.render(
1381 - <App
1382 - getSnapshotFirst={getSnapshotA}
1383 - getSnapshotSecond={getSnapshotA}
1384 - />,
1385 - );
1386 - // Render and finish the tree, but yield right after paint, before
1387 - // the passive effects have fired.
1388 - await waitForPaint([]);
1389 -
1390 - // Now mutate A. Both hooks should update.
1391 - // This is at high priority so that it doesn't get batched with default
1392 - // priority updates that might fire during the passive effect
1393 - await act(() => {
1394 - ReactNoop.discreteUpdates(() => {
1395 - mutateA('a1');
1396 - });
1397 - });
1398 -
1399 - expect(root).toMatchRenderedOutput('first: a1, second: a1');
1400 - });
1401 -
1402 - expect(root.getChildrenAsJSX()).toEqual('first: a1, second: a1');
1403 - });
1404 -
1405 - // @gate enableUseMutableSource
1406 - it(
1407 - 'if source is mutated after initial read but before subscription is set ' +
1408 - 'up, should still entangle all pending mutations even if snapshot of ' +
1409 - 'new subscription happens to match',
1410 - async () => {
1411 - const source = createSource({
1412 - a: 'a0',
1413 - b: 'b0',
1414 - });
1415 - const mutableSource = createMutableSource(source, param => param.version);
1416 -
1417 - const getSnapshotA = () => source.value.a;
1418 - const getSnapshotB = () => source.value.b;
1419 -
1420 - function mutateA(newA) {
1421 - source.value = {
1422 - ...source.value,
1423 - a: newA,
1424 - };
1425 - }
1426 -
1427 - function mutateB(newB) {
1428 - source.value = {
1429 - ...source.value,
1430 - b: newB,
1431 - };
1432 - }
1433 -
1434 - function Read({getSnapshot}) {
1435 - const value = useMutableSource(
1436 - mutableSource,
1437 - getSnapshot,
1438 - defaultSubscribe,
1439 - );
1440 - Scheduler.log(value);
1441 - return value;
1442 - }
1443 -
1444 - function Text({text}) {
1445 - Scheduler.log(text);
1446 - return text;
1447 - }
1448 -
1449 - const root = ReactNoop.createRoot();
1450 - await act(() => {
1451 - root.render(
1452 - <>
1453 - <Read getSnapshot={getSnapshotA} />
1454 - </>,
1455 - );
1456 - });
1457 - assertLog(['a0']);
1458 - expect(root).toMatchRenderedOutput('a0');
1459 -
1460 - await act(async () => {
1461 - React.startTransition(() => {
1462 - root.render(
1463 - <>
1464 - <Read getSnapshot={getSnapshotA} />
1465 - <Read getSnapshot={getSnapshotB} />
1466 - <Text text="c" />
1467 - </>,
1468 - );
1469 - });
1470 -
1471 - await waitFor(['a0', 'b0']);
1472 - // Mutate in an event. This schedules a subscription update on a, which
1473 - // already mounted, but not b, which hasn't subscribed yet.
1474 - if (gate(flags => flags.enableUnifiedSyncLane)) {
1475 - React.startTransition(() => {
1476 - mutateA('a1');
1477 - mutateB('b1');
1478 - });
1479 - } else {
1480 - mutateA('a1');
1481 - mutateB('b1');
1482 - }
1483 -
1484 - // Mutate again at lower priority. This will schedule another subscription
1485 - // update on a, but not b. When b mounts and subscriptions, the value it
1486 - // read during render will happen to match the latest value. But it should
1487 - // still entangle the updates to prevent the previous update (a1) from
1488 - // rendering by itself.
1489 - React.startTransition(() => {
1490 - mutateA('a0');
1491 - mutateB('b0');
1492 - });
1493 - // Finish the current render
1494 - await waitForPaint(['c']);
1495 - // a0 will re-render because of the mutation update. But it should show
1496 - // the latest value, not the intermediate one, to avoid tearing with b.
1497 - await waitForPaint(['a0']);
1498 -
1499 - expect(root).toMatchRenderedOutput('a0b0c');
1500 - // We should be done.
1501 - await waitForAll([]);
1502 - expect(root).toMatchRenderedOutput('a0b0c');
1503 - });
1504 - },
1505 - );
1506 -
1507 - // @gate enableUseMutableSource
1508 - it('warns about functions being used as snapshot values', async () => {
1509 - const source = createSource(() => 'a');
1510 - const mutableSource = createMutableSource(source, param => param.version);
1511 -
1512 - const getSnapshot = () => source.value;
1513 -
1514 - function Read() {
1515 - const fn = useMutableSource(mutableSource, getSnapshot, defaultSubscribe);
1516 - const value = fn();
1517 - Scheduler.log(value);
1518 - return value;
1519 - }
1520 -
1521 - const root = ReactNoop.createRoot();
1522 - root.render(
1523 - <>
1524 - <Read />
1525 - </>,
1526 - );
1527 - await expect(async () => await waitForAll(['a'])).toErrorDev(
1528 - 'Mutable source should not return a function as the snapshot value.',
1529 - );
1530 - expect(root).toMatchRenderedOutput('a');
1531 - });
1532 -
1533 - // @gate enableUseMutableSource
1534 - it('getSnapshot changes and then source is mutated during interleaved event', async () => {
1535 - const {useEffect} = React;
1536 -
1537 - const source = createComplexSource('1', '2');
1538 - const mutableSource = createMutableSource(source, param => param.version);
1539 -
1540 - // Subscribe to part of the store.
1541 - const getSnapshotA = s => s.valueA;
1542 - const subscribeA = (s, callback) => s.subscribeA(callback);
1543 - const configA = [getSnapshotA, subscribeA];
1544 -
1545 - const getSnapshotB = s => s.valueB;
1546 - const subscribeB = (s, callback) => s.subscribeB(callback);
1547 - const configB = [getSnapshotB, subscribeB];
1548 -
1549 - function App({parentConfig, childConfig}) {
1550 - const [getSnapshot, subscribe] = parentConfig;
1551 - const parentValue = useMutableSource(
1552 - mutableSource,
1553 - getSnapshot,
1554 - subscribe,
1555 - );
1556 -
1557 - Scheduler.log('Parent: ' + parentValue);
1558 -
1559 - return (
1560 - <Child
1561 - parentConfig={parentConfig}
1562 - childConfig={childConfig}
1563 - parentValue={parentValue}
1564 - />
1565 - );
1566 - }
1567 -
1568 - function Child({parentConfig, childConfig, parentValue}) {
1569 - const [getSnapshot, subscribe] = childConfig;
1570 - const childValue = useMutableSource(
1571 - mutableSource,
1572 - getSnapshot,
1573 - subscribe,
1574 - );
1575 -
1576 - Scheduler.log('Child: ' + childValue);
1577 -
1578 - let result = `${parentValue}, ${childValue}`;
1579 -
1580 - if (parentConfig === childConfig) {
1581 - // When both components read using the same config, the two values
1582 - // must be consistent.
1583 - if (parentValue !== childValue) {
1584 - result = 'Oops, tearing!';
1585 - }
1586 - }
1587 -
1588 - useEffect(() => {
1589 - Scheduler.log('Commit: ' + result);
1590 - }, [result]);
1591 -
1592 - return result;
1593 - }
1594 -
1595 - const root = ReactNoop.createRoot();
1596 - await act(() => {
1597 - root.render(<App parentConfig={configA} childConfig={configB} />);
1598 - });
1599 - assertLog(['Parent: 1', 'Child: 2', 'Commit: 1, 2']);
1600 -
1601 - await act(async () => {
1602 - // Switch the parent and the child to read using the same config
1603 - React.startTransition(() => {
1604 - root.render(<App parentConfig={configB} childConfig={configB} />);
1605 - });
1606 - // Start rendering the parent, but yield before rendering the child
1607 - await waitFor(['Parent: 2']);
1608 -
1609 - // Mutate the config. This is at lower priority so that 1) to make sure
1610 - // it doesn't happen to get batched with the in-progress render, and 2)
1611 - // so it doesn't interrupt the in-progress render.
1612 - React.startTransition(() => {
1613 - source.valueB = '3';
1614 - });
1615 -
1616 - if (gate(flags => flags.forceConcurrentByDefaultForTesting)) {
1617 - await waitFor([
1618 - // The partial render completes
1619 - 'Child: 2',
1620 - 'Commit: 2, 2',
1621 - ]);
1622 -
1623 - // Now there are two pending mutations at different priorities. But they
1624 - // both read the same version of the mutable source, so we must render
1625 - // them simultaneously.
1626 - //
1627 - await waitFor([
1628 - 'Parent: 3',
1629 - // Demonstrates that we can yield here
1630 - ]);
1631 - await waitFor([
1632 - // Now finish the rest of the update
1633 - 'Child: 3',
1634 - 'Commit: 3, 3',
1635 - ]);
1636 - } else {
1637 - // In default sync mode, all of the updates flush sync.
1638 - await waitFor([
1639 - // The partial render completes
1640 - 'Child: 2',
1641 - 'Commit: 2, 2',
1642 - 'Parent: 3',
1643 - 'Child: 3',
1644 - ]);
1645 -
1646 - await waitForAll([
1647 - // Now finish the rest of the update
1648 - 'Commit: 3, 3',
1649 - ]);
1650 - }
1651 - });
1652 - });
1653 -
1654 - // @gate enableUseMutableSource
1655 - it('should not tear with newly mounted component when updates were scheduled at a lower priority', async () => {
1656 - const source = createSource('one');
1657 - const mutableSource = createMutableSource(source, param => param.version);
1658 -
1659 - let committedA = null;
1660 - let committedB = null;
1661 -
1662 - const onRender = () => {
1663 - if (committedB !== null) {
1664 - expect(committedA).toBe(committedB);
1665 - }
1666 - };
1667 -
1668 - function ComponentA() {
1669 - const snapshot = useMutableSource(
1670 - mutableSource,
1671 - defaultGetSnapshot,
1672 - defaultSubscribe,
1673 - );
1674 - Scheduler.log(`a:${snapshot}`);
1675 - React.useEffect(() => {
1676 - committedA = snapshot;
1677 - }, [snapshot]);
1678 - return <div>{`a:${snapshot}`}</div>;
1679 - }
1680 - function ComponentB() {
1681 - const snapshot = useMutableSource(
1682 - mutableSource,
1683 - defaultGetSnapshot,
1684 - defaultSubscribe,
1685 - );
1686 - Scheduler.log(`b:${snapshot}`);
1687 - React.useEffect(() => {
1688 - committedB = snapshot;
1689 - }, [snapshot]);
1690 - return <div>{`b:${snapshot}`}</div>;
1691 - }
1692 -
1693 - // Mount ComponentA with data version 1
1694 - await act(() => {
1695 - ReactNoop.render(
1696 - <React.Profiler id="root" onRender={onRender}>
1697 - <ComponentA />
1698 - </React.Profiler>,
1699 - () => Scheduler.log('Sync effect'),
1700 - );
1701 - });
1702 - assertLog(['a:one', 'Sync effect']);
1703 - expect(source.listenerCount).toBe(1);
1704 -
1705 - // Mount ComponentB with version 1 (but don't commit it)
1706 - await act(async () => {
1707 - ReactNoop.render(
1708 - <React.Profiler id="root" onRender={onRender}>
1709 - <ComponentA />
1710 - <ComponentB />
1711 - </React.Profiler>,
1712 - () => Scheduler.log('Sync effect'),
1713 - );
1714 - await waitFor(['a:one', 'b:one', 'Sync effect']);
1715 - expect(source.listenerCount).toBe(1);
1716 -
1717 - // Mutate -> schedule update for ComponentA
1718 - React.startTransition(() => {
1719 - source.value = 'two';
1720 - });
1721 -
1722 - // Commit ComponentB -> notice the change and schedule an update for ComponentB
1723 - await waitForAll(['a:two', 'b:two']);
1724 - expect(source.listenerCount).toBe(2);
1725 - });
1726 - });
1727 -
1728 - if (__DEV__) {
1729 - describe('dev warnings', () => {
1730 - // @gate enableUseMutableSource
1731 - it('should warn if the subscribe function does not return an unsubscribe function', async () => {
1732 - const source = createSource('one');
1733 - const mutableSource = createMutableSource(
1734 - source,
1735 - param => param.version,
1736 - );
1737 -
1738 - const brokenSubscribe = () => {};
1739 -
1740 - await expect(async () => {
1741 - await act(() => {
1742 - ReactNoop.render(
1743 - <Component
1744 - label="only"
1745 - getSnapshot={defaultGetSnapshot}
1746 - mutableSource={mutableSource}
1747 - subscribe={brokenSubscribe}
1748 - />,
1749 - );
1750 - });
1751 - }).toErrorDev(
1752 - 'Mutable source subscribe function must return an unsubscribe function.',
1753 - );
1754 - });
1755 -
1756 - // @gate enableUseMutableSource
1757 - it('should error if multiple renderers of the same type use a mutable source at the same time', async () => {
1758 - const source = createSource('one');
1759 - const mutableSource = createMutableSource(
1760 - source,
1761 - param => param.version,
1762 - );
1763 -
1764 - await act(async () => {
1765 - // Start a render that uses the mutable source.
1766 - React.startTransition(() => {
1767 - ReactNoop.render(
1768 - <>
1769 - <Component
1770 - label="a"
1771 - getSnapshot={defaultGetSnapshot}
1772 - mutableSource={mutableSource}
1773 - subscribe={defaultSubscribe}
1774 - />
1775 - <Component
1776 - label="b"
1777 - getSnapshot={defaultGetSnapshot}
1778 - mutableSource={mutableSource}
1779 - subscribe={defaultSubscribe}
1780 - />
1781 - </>,
1782 - );
1783 - });
1784 -
1785 - await waitFor(['a:one']);
1786 -
1787 - const PrevScheduler = Scheduler;
1788 -
1789 - // Get a new copy of ReactNoop.
1790 - loadModules();
1791 -
1792 - spyOnDev(console, 'error').mockImplementation(() => {});
1793 -
1794 - // Use the mutablesource again but with a different renderer.
1795 - ReactNoop.render(
1796 - <Component
1797 - label="c"
1798 - getSnapshot={defaultGetSnapshot}
1799 - mutableSource={mutableSource}
1800 - subscribe={defaultSubscribe}
1801 - />,
1802 - );
1803 - await waitFor(['c:one']);
1804 -
1805 - expect(console.error.mock.calls[0][0]).toContain(
1806 - 'Detected multiple renderers concurrently rendering the ' +
1807 - 'same mutable source. This is currently unsupported.',
1808 - );
1809 -
1810 - // TODO (useMutableSource) Act will automatically flush remaining work from render 1,
1811 - // but at this point something in the hooks dispatcher has been broken by jest.resetModules()
1812 - // Figure out what this is and remove this catch.
1813 - expect(() =>
1814 - PrevScheduler.unstable_flushAllWithoutAsserting(),
1815 - ).toThrow('Invalid hook call');
1816 - });
1817 - });
1818 -
1819 - // @gate enableUseMutableSource
1820 - it('should error if multiple renderers of the same type use a mutable source at the same time with mutation between', async () => {
1821 - const source = createSource('one');
1822 - const mutableSource = createMutableSource(
1823 - source,
1824 - param => param.version,
1825 - );
1826 -
1827 - await act(async () => {
1828 - // Start a render that uses the mutable source.
1829 - React.startTransition(() => {
1830 - ReactNoop.render(
1831 - <>
1832 - <Component
1833 - label="a"
1834 - getSnapshot={defaultGetSnapshot}
1835 - mutableSource={mutableSource}
1836 - subscribe={defaultSubscribe}
1837 - />
1838 - <Component
1839 - label="b"
1840 - getSnapshot={defaultGetSnapshot}
1841 - mutableSource={mutableSource}
1842 - subscribe={defaultSubscribe}
1843 - />
1844 - </>,
1845 - );
1846 - });
1847 -
1848 - await waitFor(['a:one']);
1849 -
1850 - const PrevScheduler = Scheduler;
1851 -
1852 - // Get a new copy of ReactNoop.
1853 - loadModules();
1854 -
1855 - spyOnDev(console, 'error').mockImplementation(() => {});
1856 -
1857 - // Mutate before the new render reads from the source.
1858 - source.value = 'two';
1859 -
1860 - // Use the mutablesource again but with a different renderer.
1861 - ReactNoop.render(
1862 - <Component
1863 - label="c"
1864 - getSnapshot={defaultGetSnapshot}
1865 - mutableSource={mutableSource}
1866 - subscribe={defaultSubscribe}
1867 - />,
1868 - );
1869 - await waitFor(['c:two']);
1870 -
1871 - expect(console.error.mock.calls[0][0]).toContain(
1872 - 'Detected multiple renderers concurrently rendering the ' +
1873 - 'same mutable source. This is currently unsupported.',
1874 - );
1875 -
1876 - // TODO (useMutableSource) Act will automatically flush remaining work from render 1,
1877 - // but at this point something in the hooks dispatcher has been broken by jest.resetModules()
1878 - // Figure out what this is and remove this catch.
1879 - expect(() =>
1880 - PrevScheduler.unstable_flushAllWithoutAsserting(),
1881 - ).toThrow('Invalid hook call');
1882 - });
1883 - });
1884 - });
1885 - }
1886 -});
packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js deleted
-379
@@ -1,379 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 ReactDOMClient;
14 -let ReactDOMServer;
15 -let Scheduler;
16 -let act;
17 -let createMutableSource;
18 -let useMutableSource;
19 -let waitFor;
20 -let assertLog;
21 -
22 -describe('useMutableSourceHydration', () => {
23 - beforeEach(() => {
24 - jest.resetModules();
25 -
26 - React = require('react');
27 - ReactDOMClient = require('react-dom/client');
28 - ReactDOMServer = require('react-dom/server');
29 - Scheduler = require('scheduler');
30 -
31 - act = require('internal-test-utils').act;
32 -
33 - // Stable entrypoints export with "unstable_" prefix.
34 - createMutableSource =
35 - React.createMutableSource || React.unstable_createMutableSource;
36 - useMutableSource =
37 - React.useMutableSource || React.unstable_useMutableSource;
38 -
39 - const InternalTestUtils = require('internal-test-utils');
40 - waitFor = InternalTestUtils.waitFor;
41 - assertLog = InternalTestUtils.assertLog;
42 - });
43 -
44 - const defaultGetSnapshot = source => source.value;
45 - const defaultSubscribe = (source, callback) => source.subscribe(callback);
46 -
47 - function createComplexSource(initialValueA, initialValueB) {
48 - const callbacksA = [];
49 - const callbacksB = [];
50 - let revision = 0;
51 - let valueA = initialValueA;
52 - let valueB = initialValueB;
53 -
54 - const subscribeHelper = (callbacks, callback) => {
55 - if (callbacks.indexOf(callback) < 0) {
56 - callbacks.push(callback);
57 - }
58 - return () => {
59 - const index = callbacks.indexOf(callback);
60 - if (index >= 0) {
61 - callbacks.splice(index, 1);
62 - }
63 - };
64 - };
65 -
66 - return {
67 - subscribeA(callback) {
68 - return subscribeHelper(callbacksA, callback);
69 - },
70 - subscribeB(callback) {
71 - return subscribeHelper(callbacksB, callback);
72 - },
73 -
74 - get listenerCountA() {
75 - return callbacksA.length;
76 - },
77 - get listenerCountB() {
78 - return callbacksB.length;
79 - },
80 -
81 - set valueA(newValue) {
82 - revision++;
83 - valueA = newValue;
84 - callbacksA.forEach(callback => callback());
85 - },
86 - get valueA() {
87 - return valueA;
88 - },
89 -
90 - set valueB(newValue) {
91 - revision++;
92 - valueB = newValue;
93 - callbacksB.forEach(callback => callback());
94 - },
95 - get valueB() {
96 - return valueB;
97 - },
98 -
99 - get version() {
100 - return revision;
101 - },
102 - };
103 - }
104 -
105 - function createSource(initialValue) {
106 - const callbacks = [];
107 - let revision = 0;
108 - let value = initialValue;
109 - return {
110 - subscribe(callback) {
111 - if (callbacks.indexOf(callback) < 0) {
112 - callbacks.push(callback);
113 - }
114 - return () => {
115 - const index = callbacks.indexOf(callback);
116 - if (index >= 0) {
117 - callbacks.splice(index, 1);
118 - }
119 - };
120 - },
121 - get listenerCount() {
122 - return callbacks.length;
123 - },
124 - set value(newValue) {
125 - revision++;
126 - value = newValue;
127 - callbacks.forEach(callback => callback());
128 - },
129 - get value() {
130 - return value;
131 - },
132 - get version() {
133 - return revision;
134 - },
135 - };
136 - }
137 -
138 - function Component({getSnapshot, label, mutableSource, subscribe}) {
139 - const snapshot = useMutableSource(mutableSource, getSnapshot, subscribe);
140 - Scheduler.log(`${label}:${snapshot}`);
141 - return <div>{`${label}:${snapshot}`}</div>;
142 - }
143 -
144 - // @gate enableUseMutableSource
145 - it('should render and hydrate', async () => {
146 - const source = createSource('one');
147 - const mutableSource = createMutableSource(source, param => param.version);
148 -
149 - function TestComponent() {
150 - return (
151 - <Component
152 - label="only"
153 - getSnapshot={defaultGetSnapshot}
154 - mutableSource={mutableSource}
155 - subscribe={defaultSubscribe}
156 - />
157 - );
158 - }
159 -
160 - const container = document.createElement('div');
161 - document.body.appendChild(container);
162 -
163 - const htmlString = ReactDOMServer.renderToString(<TestComponent />);
164 - container.innerHTML = htmlString;
165 - assertLog(['only:one']);
166 - expect(source.listenerCount).toBe(0);
167 -
168 - await act(() => {
169 - ReactDOMClient.hydrateRoot(container, <TestComponent />, {
170 - mutableSources: [mutableSource],
171 - });
172 - });
173 - assertLog(['only:one']);
174 - expect(source.listenerCount).toBe(1);
175 - });
176 -
177 - // @gate enableUseMutableSource
178 - // @gate enableClientRenderFallbackOnTextMismatch
179 - it('should detect a tear before hydrating a component', async () => {
180 - const source = createSource('one');
181 - const mutableSource = createMutableSource(source, param => param.version);
182 -
183 - function TestComponent() {
184 - return (
185 - <Component
186 - label="only"
187 - getSnapshot={defaultGetSnapshot}
188 - mutableSource={mutableSource}
189 - subscribe={defaultSubscribe}
190 - />
191 - );
192 - }
193 -
194 - const container = document.createElement('div');
195 - document.body.appendChild(container);
196 -
197 - const htmlString = ReactDOMServer.renderToString(<TestComponent />);
198 - container.innerHTML = htmlString;
199 - assertLog(['only:one']);
200 - expect(source.listenerCount).toBe(0);
201 -
202 - await expect(async () => {
203 - await act(() => {
204 - ReactDOMClient.hydrateRoot(container, <TestComponent />, {
205 - mutableSources: [mutableSource],
206 - onRecoverableError(error) {
207 - Scheduler.log('Log error: ' + error.message);
208 - },
209 - });
210 -
211 - source.value = 'two';
212 - });
213 - }).toErrorDev(
214 - [
215 - 'Warning: Text content did not match. Server: "only:one" Client: "only:two"',
216 - 'Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.',
217 - ],
218 - {withoutStack: 1},
219 - );
220 - assertLog([
221 - 'only:two',
222 - 'only:two',
223 - 'Log error: Text content does not match server-rendered HTML.',
224 - 'Log error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
225 - ]);
226 - expect(source.listenerCount).toBe(1);
227 - });
228 -
229 - // @gate enableUseMutableSource
230 - it('should detect a tear between hydrating components', async () => {
231 - const source = createSource('one');
232 - const mutableSource = createMutableSource(source, param => param.version);
233 -
234 - function TestComponent() {
235 - return (
236 - <>
237 - <Component
238 - label="a"
239 - getSnapshot={defaultGetSnapshot}
240 - mutableSource={mutableSource}
241 - subscribe={defaultSubscribe}
242 - />
243 - <Component
244 - label="b"
245 - getSnapshot={defaultGetSnapshot}
246 - mutableSource={mutableSource}
247 - subscribe={defaultSubscribe}
248 - />
249 - </>
250 - );
251 - }
252 -
253 - const container = document.createElement('div');
254 - document.body.appendChild(container);
255 -
256 - const htmlString = ReactDOMServer.renderToString(<TestComponent />);
257 - container.innerHTML = htmlString;
258 - assertLog(['a:one', 'b:one']);
259 - expect(source.listenerCount).toBe(0);
260 -
261 - await expect(async () => {
262 - await act(async () => {
263 - React.startTransition(() => {
264 - ReactDOMClient.hydrateRoot(container, <TestComponent />, {
265 - mutableSources: [mutableSource],
266 - onRecoverableError(error) {
267 - Scheduler.log('Log error: ' + error.message);
268 - },
269 - });
270 - });
271 -
272 - await waitFor(['a:one']);
273 - source.value = 'two';
274 - });
275 - }).toErrorDev(
276 - 'Warning: An error occurred during hydration. ' +
277 - 'The server HTML was replaced with client content in <div>.',
278 - {withoutStack: true},
279 - );
280 - assertLog([
281 - 'a:two',
282 - 'b:two',
283 - // TODO: Before onRecoverableError, this error was never surfaced to the
284 - // user. The request to file an bug report no longer makes sense.
285 - // However, the experimental useMutableSource API is slated for
286 - // removal, anyway.
287 - 'Log error: Cannot read from mutable source during the current ' +
288 - 'render without tearing. This may be a bug in React. Please file ' +
289 - 'an issue.',
290 - 'Log error: There was an error while hydrating. Because the error ' +
291 - 'happened outside of a Suspense boundary, the entire root will ' +
292 - 'switch to client rendering.',
293 - ]);
294 - expect(source.listenerCount).toBe(2);
295 - });
296 -
297 - // @gate enableUseMutableSource
298 - it('should detect a tear between hydrating components reading from different parts of a source', async () => {
299 - const source = createComplexSource('a:one', 'b:one');
300 - const mutableSource = createMutableSource(source, param => param.version);
301 -
302 - // Subscribe to part of the store.
303 - const getSnapshotA = s => s.valueA;
304 - const subscribeA = (s, callback) => s.subscribeA(callback);
305 - const getSnapshotB = s => s.valueB;
306 - const subscribeB = (s, callback) => s.subscribeB(callback);
307 -
308 - const container = document.createElement('div');
309 - document.body.appendChild(container);
310 -
311 - const htmlString = ReactDOMServer.renderToString(
312 - <>
313 - <Component
314 - label="0"
315 - getSnapshot={getSnapshotA}
316 - mutableSource={mutableSource}
317 - subscribe={subscribeA}
318 - />
319 - <Component
320 - label="1"
321 - getSnapshot={getSnapshotB}
322 - mutableSource={mutableSource}
323 - subscribe={subscribeB}
324 - />
325 - </>,
326 - );
327 - container.innerHTML = htmlString;
328 - assertLog(['0:a:one', '1:b:one']);
329 -
330 - await expect(async () => {
331 - await act(async () => {
332 - const fragment = (
333 - <>
334 - <Component
335 - label="0"
336 - getSnapshot={getSnapshotA}
337 - mutableSource={mutableSource}
338 - subscribe={subscribeA}
339 - />
340 - <Component
341 - label="1"
342 - getSnapshot={getSnapshotB}
343 - mutableSource={mutableSource}
344 - subscribe={subscribeB}
345 - />
346 - </>
347 - );
348 - React.startTransition(() => {
349 - ReactDOMClient.hydrateRoot(container, fragment, {
350 - mutableSources: [mutableSource],
351 - onRecoverableError(error) {
352 - Scheduler.log('Log error: ' + error.message);
353 - },
354 - });
355 - });
356 - await waitFor(['0:a:one']);
357 - source.valueB = 'b:two';
358 - });
359 - }).toErrorDev(
360 - 'Warning: An error occurred during hydration. ' +
361 - 'The server HTML was replaced with client content in <div>.',
362 - {withoutStack: true},
363 - );
364 - assertLog([
365 - '0:a:one',
366 - '1:b:two',
367 - // TODO: Before onRecoverableError, this error was never surfaced to the
368 - // user. The request to file an bug report no longer makes sense.
369 - // However, the experimental useMutableSource API is slated for
370 - // removal, anyway.
371 - 'Log error: Cannot read from mutable source during the current ' +
372 - 'render without tearing. This may be a bug in React. Please file ' +
373 - 'an issue.',
374 - 'Log error: There was an error while hydrating. Because the error ' +
375 - 'happened outside of a Suspense boundary, the entire root will ' +
376 - 'switch to client rendering.',
377 - ]);
378 - });
379 -});
packages/react-server/src/ReactFizzHooks.js
-16
@@ -10,9 +10,6 @@
10 import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
11
12 import type {
13 - MutableSource,
14 - MutableSourceGetSnapshotFn,
15 - MutableSourceSubscribeFn,
13 ReactContext,
14 StartTransitionOptions,
15 Thenable,
@@ -505,18 +502,6 @@ export function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
502 return throwOnUseEffectEventCall;
503 }
504
508 -// TODO Decide on how to implement this hook for server rendering.
509 -// If a mutation occurs during render, consider triggering a Suspense boundary
510 -// and falling back to client rendering.
511 -function useMutableSource<Source, Snapshot>(
512 - source: MutableSource<Source>,
513 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
514 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
515 -): Snapshot {
516 - resolveCurrentlyRenderingComponent();
517 - return getSnapshot(source._source);
518 -}
519 -
505 function useSyncExternalStore<T>(
506 subscribe: (() => void) => () => void,
507 getSnapshot: () => T,
@@ -648,7 +633,6 @@ export const HooksDispatcher: Dispatcher = {
633 useTransition,
634 useId,
635 // Subscriptions are not setup in a server environment.
651 - useMutableSource,
636 useSyncExternalStore,
637 };
638
packages/react-server/src/ReactFlightHooks.js
-1
@@ -87,7 +87,6 @@ export const HooksDispatcher: Dispatcher = {
87 useImperativeHandle: (unsupportedHook: any),
88 useEffect: (unsupportedHook: any),
89 useId,
90 - useMutableSource: (unsupportedHook: any),
90 useSyncExternalStore: (unsupportedHook: any),
91 useCacheRefresh(): <T>(?() => T, ?T) => void {
92 return unsupportedRefresh;
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 createServerContext,
28 use,
@@ -57,8 +55,6 @@ export {
55 useLayoutEffect,
56 useInsertionEffect,
57 useMemo,
60 - useMutableSource,
61 - useMutableSource as unstable_useMutableSource,
58 experimental_useOptimistic,
59 useReducer,
60 useRef,
packages/react/index.js
-2
@@ -45,7 +45,6 @@ export {
45 createContext,
46 createElement,
47 createFactory,
48 - createMutableSource,
48 createRef,
49 createServerContext,
50 use,
@@ -76,7 +75,6 @@ export {
75 useInsertionEffect,
76 useLayoutEffect,
77 useMemo,
79 - useMutableSource,
78 experimental_useOptimistic,
79 useSyncExternalStore,
80 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 createServerContext,
27 use,
@@ -55,8 +53,6 @@ export {
53 useInsertionEffect,
54 useLayoutEffect,
55 useMemo,
58 - useMutableSource,
59 - useMutableSource as unstable_useMutableSource,
56 experimental_useOptimistic,
57 useReducer,
58 useRef,
packages/react/src/React.js
-4
@@ -48,7 +48,6 @@ import {
48 useInsertionEffect,
49 useLayoutEffect,
50 useMemo,
51 - useMutableSource,
51 useSyncExternalStore,
52 useReducer,
53 useRef,
@@ -67,7 +66,6 @@ import {
66 cloneElementWithValidation,
67 } from './ReactElementValidator';
68 import {createServerContext} from './ReactServerContext';
70 -import {createMutableSource} from './ReactMutableSource';
69 import ReactSharedInternals from './ReactSharedInternals';
70 import {startTransition} from './ReactStartTransition';
71 import {act} from './ReactAct';
@@ -93,7 +91,6 @@ const Children = {
91
92 export {
93 Children,
96 - createMutableSource,
94 createRef,
95 Component,
96 PureComponent,
@@ -112,7 +109,6 @@ export {
109 useInsertionEffect,
110 useLayoutEffect,
111 useMemo,
115 - useMutableSource,
112 useOptimistic as experimental_useOptimistic,
113 useSyncExternalStore,
114 useReducer,
packages/react/src/ReactHooks.js
-12
@@ -9,9 +9,6 @@
9
10 import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
11 import type {
12 - MutableSource,
13 - MutableSourceGetSnapshotFn,
14 - MutableSourceSubscribeFn,
12 ReactContext,
13 StartTransitionOptions,
14 Usable,
@@ -194,15 +191,6 @@ export function useId(): string {
191 return dispatcher.useId();
192 }
193
197 -export function useMutableSource<Source, Snapshot>(
198 - source: MutableSource<Source>,
199 - getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
200 - subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
201 -): Snapshot {
202 - const dispatcher = resolveDispatcher();
203 - return dispatcher.useMutableSource(source, getSnapshot, subscribe);
204 -}
205 -
194 export function useSyncExternalStore<T>(
195 subscribe: (() => void) => () => void,
196 getSnapshot: () => T,
packages/react/src/ReactMutableSource.js deleted
-34
@@ -1,34 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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/shared/ReactFeatureFlags.js
-3
@@ -32,9 +32,6 @@ export const enableComponentStackLocations = true;
32 // TODO: Finish rolling out in www
33 export const enableClientRenderFallbackOnTextMismatch = true;
34
35 -// Recoil still uses useMutableSource in www, need to delete
36 -export const enableUseMutableSource = false;
37 -
35 // Not sure if www still uses this. We don't have a replacement but whatever we
36 // replace it with will likely be different than what's already there, so we
37 // probably should just delete it as long as nothing in www relies on it.
packages/shared/ReactTypes.js
-52
@@ -7,8 +7,6 @@
7 * @flow
8 */
9
10 -import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
11 -
10 export type ReactNode =
11 | React$Element<any>
12 | ReactPortal
@@ -117,56 +115,6 @@ export type ReactScopeInstance = {
115 getChildContextValues: <T>(context: ReactContext<T>) => Array<T>,
116 };
117
120 -// Mutable source version can be anything (e.g. number, string, immutable data structure)
121 -// so long as it changes every time any part of the source changes.
122 -export type MutableSourceVersion = $NonMaybeType<mixed>;
123 -
124 -export type MutableSourceGetSnapshotFn<
125 - Source: $NonMaybeType<mixed>,
126 - Snapshot,
127 -> = (source: Source) => Snapshot;
128 -
129 -export type MutableSourceSubscribeFn<Source: $NonMaybeType<mixed>, Snapshot> = (
130 - source: Source,
131 - callback: (snapshot: Snapshot) => void,
132 -) => () => void;
133 -
134 -export type MutableSourceGetVersionFn = (
135 - source: $NonMaybeType<mixed>,
136 -) => MutableSourceVersion;
137 -
138 -export type MutableSource<Source: $NonMaybeType<mixed>> = {
139 - _source: Source,
140 -
141 - _getVersion: MutableSourceGetVersionFn,
142 -
143 - // Tracks the version of this source at the time it was most recently read.
144 - // Used to determine if a source is safe to read from before it has been subscribed to.
145 - // Version number is only used during mount,
146 - // since the mechanism for determining safety after subscription is expiration time.
147 - //
148 - // As a workaround to support multiple concurrent renderers,
149 - // we categorize some renderers as primary and others as secondary.
150 - // We only expect there to be two concurrent renderers at most:
151 - // React Native (primary) and Fabric (secondary);
152 - // React DOM (primary) and React ART (secondary).
153 - // Secondary renderers store their context values on separate fields.
154 - // We use the same approach for Context.
155 - _workInProgressVersionPrimary: null | MutableSourceVersion,
156 - _workInProgressVersionSecondary: null | MutableSourceVersion,
157 -
158 - // DEV only
159 - // Used to detect multiple renderers using the same mutable source.
160 - _currentPrimaryRenderer?: Object | null,
161 - _currentSecondaryRenderer?: Object | null,
162 -
163 - // DEV only
164 - // Used to detect side effects that update a mutable source during render.
165 - // See https://github.com/facebook/react/issues/19948
166 - _currentlyRenderingFiber?: Fiber | null,
167 - _initialVersionAsOfFirstRender?: MutableSourceVersion | null,
168 -};
169 -
118 // The subset of a Thenable required by things thrown by Suspense.
119 // This doesn't require a value to be passed to either handler.
120 export interface Wakeable {
packages/shared/forks/ReactFeatureFlags.native-fb.js
-2
@@ -74,8 +74,6 @@ export const enableCustomElementPropertySupport = false;
74 export const consoleManagedByDevToolsDuringStrictMode = false;
75 export const enableServerContext = true;
76
77 -export const enableUseMutableSource = true;
78 -
77 export const enableTransitionTracing = false;
78
79 export const enableFloat = true;
packages/shared/forks/ReactFeatureFlags.native-oss.js
-1
@@ -60,7 +60,6 @@ export const enableCustomElementPropertySupport = false;
60
61 export const consoleManagedByDevToolsDuringStrictMode = false;
62 export const enableServerContext = true;
63 -export const enableUseMutableSource = false;
63
64 export const enableTransitionTracing = false;
65
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-1
@@ -60,7 +60,6 @@ export const enableCustomElementPropertySupport = false;
60
61 export const consoleManagedByDevToolsDuringStrictMode = false;
62 export const enableServerContext = true;
63 -export const enableUseMutableSource = false;
63
64 export const enableTransitionTracing = false;
65
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
-1
@@ -58,7 +58,6 @@ export const allowConcurrentByDefault = true;
58
59 export const consoleManagedByDevToolsDuringStrictMode = false;
60 export const enableServerContext = true;
61 -export const enableUseMutableSource = false;
61
62 export const enableTransitionTracing = false;
63
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
-3
@@ -61,9 +61,6 @@ export const enableCustomElementPropertySupport = false;
61 export const consoleManagedByDevToolsDuringStrictMode = false;
62 export const enableServerContext = true;
63
64 -// Some www surfaces are still using this. Remove once they have been migrated.
65 -export const enableUseMutableSource = true;
66 -
64 export const enableTransitionTracing = false;
65
66 export const enableFloat = true;
packages/shared/forks/ReactFeatureFlags.www.js
-3
@@ -100,9 +100,6 @@ export const allowConcurrentByDefault = true;
100 export const consoleManagedByDevToolsDuringStrictMode = true;
101 export const enableServerContext = true;
102
103 -// Some www surfaces are still using this. Remove once they have been migrated.
104 -export const enableUseMutableSource = true;
105 -
103 export const useModernStrictMode = false;
104 export const enableFizzExternalRuntime = true;
105