@samitouri / QOS-React-2 / commits / aea7c2aab1

Re-land "Support nesting of startTransition and flushSync (alt) (#21149)"

This re-lands commit faa1e127f1ba755da846bc6ce299cdefaf97721f.

Andrew Clark committed May 3, 2021 at 13:34 UTC aea7c2aab18f2c288a1fcab0cf7a14560b5db35e
4 files changed +93
packages/react-dom/src/events/ReactDOMEventListener.js
+6
@@ -49,6 +49,9 @@ import {
49 getCurrentUpdatePriority,
50 setCurrentUpdatePriority,
51 } from 'react-reconciler/src/ReactEventPriorities';
52 +import ReactSharedInternals from 'shared/ReactSharedInternals';
53 +
54 +const {ReactCurrentBatchConfig} = ReactSharedInternals;
55
56 // TODO: can we stop exporting these?
57 export let _enabled = true;
@@ -125,11 +128,14 @@ function dispatchContinuousEvent(
128 nativeEvent,
129 ) {
130 const previousPriority = getCurrentUpdatePriority();
131 + const prevTransition = ReactCurrentBatchConfig.transition;
132 + ReactCurrentBatchConfig.transition = 0;
133 try {
134 setCurrentUpdatePriority(ContinuousEventPriority);
135 dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
136 } finally {
137 setCurrentUpdatePriority(previousPriority);
138 + ReactCurrentBatchConfig.transition = prevTransition;
139 }
140 }
141
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+22
@@ -245,6 +245,7 @@ const ceil = Math.ceil;
245 const {
246 ReactCurrentDispatcher,
247 ReactCurrentOwner,
248 + ReactCurrentBatchConfig,
249 IsSomeRendererActing,
250 } = ReactSharedInternals;
251
@@ -1062,11 +1063,14 @@ export function flushDiscreteUpdates() {
1063
1064 export function deferredUpdates<A>(fn: () => A): A {
1065 const previousPriority = getCurrentUpdatePriority();
1066 + const prevTransition = ReactCurrentBatchConfig.transition;
1067 try {
1068 + ReactCurrentBatchConfig.transition = 0;
1069 setCurrentUpdatePriority(DefaultEventPriority);
1070 return fn();
1071 } finally {
1072 setCurrentUpdatePriority(previousPriority);
1073 + ReactCurrentBatchConfig.transition = prevTransition;
1074 }
1075 }
1076
@@ -1110,11 +1114,14 @@ export function discreteUpdates<A, B, C, D, R>(
1114 d: D,
1115 ): R {
1116 const previousPriority = getCurrentUpdatePriority();
1117 + const prevTransition = ReactCurrentBatchConfig.transition;
1118 try {
1119 + ReactCurrentBatchConfig.transition = 0;
1120 setCurrentUpdatePriority(DiscreteEventPriority);
1121 return fn(a, b, c, d);
1122 } finally {
1123 setCurrentUpdatePriority(previousPriority);
1124 + ReactCurrentBatchConfig.transition = prevTransition;
1125 if (executionContext === NoContext) {
1126 resetRenderTimer();
1127 }
@@ -1144,8 +1151,10 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1151 const prevExecutionContext = executionContext;
1152 executionContext |= BatchedContext;
1153
1154 + const prevTransition = ReactCurrentBatchConfig.transition;
1155 const previousPriority = getCurrentUpdatePriority();
1156 try {
1157 + ReactCurrentBatchConfig.transition = 0;
1158 setCurrentUpdatePriority(DiscreteEventPriority);
1159 if (fn) {
1160 return fn(a);
@@ -1154,6 +1163,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1163 }
1164 } finally {
1165 setCurrentUpdatePriority(previousPriority);
1166 + ReactCurrentBatchConfig.transition = prevTransition;
1167 executionContext = prevExecutionContext;
1168 // Flush the immediate callbacks that were scheduled during this batch.
1169 // Note that this will happen even if batchedUpdates is higher up
@@ -1175,12 +1185,15 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1185 export function flushControlled(fn: () => mixed): void {
1186 const prevExecutionContext = executionContext;
1187 executionContext |= BatchedContext;
1188 + const prevTransition = ReactCurrentBatchConfig.transition;
1189 const previousPriority = getCurrentUpdatePriority();
1190 try {
1191 + ReactCurrentBatchConfig.transition = 0;
1192 setCurrentUpdatePriority(DiscreteEventPriority);
1193 fn();
1194 } finally {
1195 setCurrentUpdatePriority(previousPriority);
1196 + ReactCurrentBatchConfig.transition = prevTransition;
1197
1198 executionContext = prevExecutionContext;
1199 if (executionContext === NoContext) {
@@ -1675,10 +1688,13 @@ function commitRoot(root) {
1688 // TODO: This no longer makes any sense. We already wrap the mutation and
1689 // layout phases. Should be able to remove.
1690 const previousUpdateLanePriority = getCurrentUpdatePriority();
1691 + const prevTransition = ReactCurrentBatchConfig.transition;
1692 try {
1693 + ReactCurrentBatchConfig.transition = 0;
1694 setCurrentUpdatePriority(DiscreteEventPriority);
1695 commitRootImpl(root, previousUpdateLanePriority);
1696 } finally {
1697 + ReactCurrentBatchConfig.transition = prevTransition;
1698 setCurrentUpdatePriority(previousUpdateLanePriority);
1699 }
1700
@@ -1800,6 +1816,8 @@ function commitRootImpl(root, renderPriorityLevel) {
1816 NoFlags;
1817
1818 if (subtreeHasEffects || rootHasEffect) {
1819 + const prevTransition = ReactCurrentBatchConfig.transition;
1820 + ReactCurrentBatchConfig.transition = 0;
1821 const previousPriority = getCurrentUpdatePriority();
1822 setCurrentUpdatePriority(DiscreteEventPriority);
1823
@@ -1881,6 +1899,7 @@ function commitRootImpl(root, renderPriorityLevel) {
1899
1900 // Reset the priority to the previous non-sync value.
1901 setCurrentUpdatePriority(previousPriority);
1902 + ReactCurrentBatchConfig.transition = prevTransition;
1903 } else {
1904 // No effects.
1905 root.current = finishedWork;
@@ -2017,12 +2036,15 @@ export function flushPassiveEffects(): boolean {
2036 if (rootWithPendingPassiveEffects !== null) {
2037 const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
2038 const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
2039 + const prevTransition = ReactCurrentBatchConfig.transition;
2040 const previousPriority = getCurrentUpdatePriority();
2041 try {
2042 + ReactCurrentBatchConfig.transition = 0;
2043 setCurrentUpdatePriority(priority);
2044 return flushPassiveEffectsImpl();
2045 } finally {
2046 setCurrentUpdatePriority(previousPriority);
2047 + ReactCurrentBatchConfig.transition = prevTransition;
2048 }
2049 }
2050 return false;
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+22
@@ -245,6 +245,7 @@ const ceil = Math.ceil;
245 const {
246 ReactCurrentDispatcher,
247 ReactCurrentOwner,
248 + ReactCurrentBatchConfig,
249 IsSomeRendererActing,
250 } = ReactSharedInternals;
251
@@ -1062,11 +1063,14 @@ export function flushDiscreteUpdates() {
1063
1064 export function deferredUpdates<A>(fn: () => A): A {
1065 const previousPriority = getCurrentUpdatePriority();
1066 + const prevTransition = ReactCurrentBatchConfig.transition;
1067 try {
1068 + ReactCurrentBatchConfig.transition = 0;
1069 setCurrentUpdatePriority(DefaultEventPriority);
1070 return fn();
1071 } finally {
1072 setCurrentUpdatePriority(previousPriority);
1073 + ReactCurrentBatchConfig.transition = prevTransition;
1074 }
1075 }
1076
@@ -1110,11 +1114,14 @@ export function discreteUpdates<A, B, C, D, R>(
1114 d: D,
1115 ): R {
1116 const previousPriority = getCurrentUpdatePriority();
1117 + const prevTransition = ReactCurrentBatchConfig.transition;
1118 try {
1119 + ReactCurrentBatchConfig.transition = 0;
1120 setCurrentUpdatePriority(DiscreteEventPriority);
1121 return fn(a, b, c, d);
1122 } finally {
1123 setCurrentUpdatePriority(previousPriority);
1124 + ReactCurrentBatchConfig.transition = prevTransition;
1125 if (executionContext === NoContext) {
1126 resetRenderTimer();
1127 }
@@ -1144,8 +1151,10 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1151 const prevExecutionContext = executionContext;
1152 executionContext |= BatchedContext;
1153
1154 + const prevTransition = ReactCurrentBatchConfig.transition;
1155 const previousPriority = getCurrentUpdatePriority();
1156 try {
1157 + ReactCurrentBatchConfig.transition = 0;
1158 setCurrentUpdatePriority(DiscreteEventPriority);
1159 if (fn) {
1160 return fn(a);
@@ -1154,6 +1163,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1163 }
1164 } finally {
1165 setCurrentUpdatePriority(previousPriority);
1166 + ReactCurrentBatchConfig.transition = prevTransition;
1167 executionContext = prevExecutionContext;
1168 // Flush the immediate callbacks that were scheduled during this batch.
1169 // Note that this will happen even if batchedUpdates is higher up
@@ -1175,12 +1185,15 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1185 export function flushControlled(fn: () => mixed): void {
1186 const prevExecutionContext = executionContext;
1187 executionContext |= BatchedContext;
1188 + const prevTransition = ReactCurrentBatchConfig.transition;
1189 const previousPriority = getCurrentUpdatePriority();
1190 try {
1191 + ReactCurrentBatchConfig.transition = 0;
1192 setCurrentUpdatePriority(DiscreteEventPriority);
1193 fn();
1194 } finally {
1195 setCurrentUpdatePriority(previousPriority);
1196 + ReactCurrentBatchConfig.transition = prevTransition;
1197
1198 executionContext = prevExecutionContext;
1199 if (executionContext === NoContext) {
@@ -1675,10 +1688,13 @@ function commitRoot(root) {
1688 // TODO: This no longer makes any sense. We already wrap the mutation and
1689 // layout phases. Should be able to remove.
1690 const previousUpdateLanePriority = getCurrentUpdatePriority();
1691 + const prevTransition = ReactCurrentBatchConfig.transition;
1692 try {
1693 + ReactCurrentBatchConfig.transition = 0;
1694 setCurrentUpdatePriority(DiscreteEventPriority);
1695 commitRootImpl(root, previousUpdateLanePriority);
1696 } finally {
1697 + ReactCurrentBatchConfig.transition = prevTransition;
1698 setCurrentUpdatePriority(previousUpdateLanePriority);
1699 }
1700
@@ -1800,6 +1816,8 @@ function commitRootImpl(root, renderPriorityLevel) {
1816 NoFlags;
1817
1818 if (subtreeHasEffects || rootHasEffect) {
1819 + const prevTransition = ReactCurrentBatchConfig.transition;
1820 + ReactCurrentBatchConfig.transition = 0;
1821 const previousPriority = getCurrentUpdatePriority();
1822 setCurrentUpdatePriority(DiscreteEventPriority);
1823
@@ -1881,6 +1899,7 @@ function commitRootImpl(root, renderPriorityLevel) {
1899
1900 // Reset the priority to the previous non-sync value.
1901 setCurrentUpdatePriority(previousPriority);
1902 + ReactCurrentBatchConfig.transition = prevTransition;
1903 } else {
1904 // No effects.
1905 root.current = finishedWork;
@@ -2017,12 +2036,15 @@ export function flushPassiveEffects(): boolean {
2036 if (rootWithPendingPassiveEffects !== null) {
2037 const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
2038 const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
2039 + const prevTransition = ReactCurrentBatchConfig.transition;
2040 const previousPriority = getCurrentUpdatePriority();
2041 try {
2042 + ReactCurrentBatchConfig.transition = 0;
2043 setCurrentUpdatePriority(priority);
2044 return flushPassiveEffectsImpl();
2045 } finally {
2046 setCurrentUpdatePriority(previousPriority);
2047 + ReactCurrentBatchConfig.transition = prevTransition;
2048 }
2049 }
2050 return false;
packages/react-reconciler/src/__tests__/ReactFlushSync-test.js
+43
@@ -3,6 +3,7 @@ let ReactNoop;
3 let Scheduler;
4 let useState;
5 let useEffect;
6 +let startTransition;
7
8 describe('ReactFlushSync', () => {
9 beforeEach(() => {
@@ -13,6 +14,7 @@ describe('ReactFlushSync', () => {
14 Scheduler = require('scheduler');
15 useState = React.useState;
16 useEffect = React.useEffect;
17 + startTransition = React.unstable_startTransition;
18 });
19
20 function Text({text}) {
@@ -62,6 +64,47 @@ describe('ReactFlushSync', () => {
64 expect(root).toMatchRenderedOutput('1, 1');
65 });
66
67 + // @gate experimental
68 + test('nested with startTransition', async () => {
69 + let setSyncState;
70 + let setState;
71 + function App() {
72 + const [syncState, _setSyncState] = useState(0);
73 + const [state, _setState] = useState(0);
74 + setSyncState = _setSyncState;
75 + setState = _setState;
76 + return <Text text={`${syncState}, ${state}`} />;
77 + }
78 +
79 + const root = ReactNoop.createRoot();
80 + await ReactNoop.act(async () => {
81 + root.render(<App />);
82 + });
83 + expect(Scheduler).toHaveYielded(['0, 0']);
84 + expect(root).toMatchRenderedOutput('0, 0');
85 +
86 + await ReactNoop.act(async () => {
87 + ReactNoop.flushSync(() => {
88 + startTransition(() => {
89 + // This should be async even though flushSync is on the stack, because
90 + // startTransition is closer.
91 + setState(1);
92 + ReactNoop.flushSync(() => {
93 + // This should be async even though startTransition is on the stack,
94 + // because flushSync is closer.
95 + setSyncState(1);
96 + });
97 + });
98 + });
99 + // Only the sync update should have flushed
100 + expect(Scheduler).toHaveYielded(['1, 0']);
101 + expect(root).toMatchRenderedOutput('1, 0');
102 + });
103 + // Now the async update has flushed, too.
104 + expect(Scheduler).toHaveYielded(['1, 1']);
105 + expect(root).toMatchRenderedOutput('1, 1');
106 + });
107 +
108 test('flushes passive effects synchronously when they are the result of a sync render', async () => {
109 function App() {
110 useEffect(() => {