@samitouri / QOS-React-2 / commits / 832e2987e0

Revert accdientally merged PR (#24081)

Andrew Clark committed Mar 11, 2022 at 21:31 UTC 832e2987e01aa357c3b2e551acae0682ca36fb14
26 files changed +288 -562
packages/react-art/src/ReactART.js
+1
@@ -69,6 +69,7 @@ class Surface extends React.Component {
69 this._mountNode = createContainer(
70 this._surface,
71 LegacyRoot,
72 + false,
73 null,
74 false,
75 false,
packages/react-dom/src/__tests__/ReactDOMFizzShellHydration-test.js
+2 -34
@@ -9,7 +9,6 @@
9
10 let JSDOM;
11 let React;
12 -let startTransition;
12 let ReactDOMClient;
13 let Scheduler;
14 let clientAct;
@@ -34,8 +33,6 @@ describe('ReactDOMFizzShellHydration', () => {
33 ReactDOMFizzServer = require('react-dom/server');
34 Stream = require('stream');
35
37 - startTransition = React.startTransition;
38 -
36 textCache = new Map();
37
38 // Test Environment
@@ -217,36 +214,7 @@ describe('ReactDOMFizzShellHydration', () => {
214 expect(container.textContent).toBe('Shell');
215 });
216
220 - test(
221 - 'updating the root at lower priority than initial hydration does not ' +
222 - 'force a client render',
223 - async () => {
224 - function App() {
225 - return <Text text="Initial" />;
226 - }
227 -
228 - // Server render
229 - await resolveText('Initial');
230 - await serverAct(async () => {
231 - const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
232 - pipe(writable);
233 - });
234 - expect(Scheduler).toHaveYielded(['Initial']);
235 -
236 - await clientAct(async () => {
237 - const root = ReactDOMClient.hydrateRoot(container, <App />);
238 - // This has lower priority than the initial hydration, so the update
239 - // won't be processed until after hydration finishes.
240 - startTransition(() => {
241 - root.render(<Text text="Updated" />);
242 - });
243 - });
244 - expect(Scheduler).toHaveYielded(['Initial', 'Updated']);
245 - expect(container.textContent).toBe('Updated');
246 - },
247 - );
248 -
249 - test('updating the root while the shell is suspended forces a client render', async () => {
217 + test('updating the root before the shell hydrates forces a client render', async () => {
218 function App() {
219 return <AsyncText text="Shell" />;
220 }
@@ -277,9 +245,9 @@ describe('ReactDOMFizzShellHydration', () => {
245 root.render(<Text text="New screen" />);
246 });
247 expect(Scheduler).toHaveYielded([
280 - 'New screen',
248 'This root received an early update, before anything was able ' +
249 'hydrate. Switched the entire root to client rendering.',
250 + 'New screen',
251 ]);
252 expect(container.textContent).toBe('New screen');
253 });
packages/react-dom/src/__tests__/ReactDOMRoot-test.js
-9
@@ -253,15 +253,6 @@ describe('ReactDOMRoot', () => {
253 );
254 });
255
256 - it('callback passed to legacy hydrate() API', () => {
257 - container.innerHTML = '<div>Hi</div>';
258 - ReactDOM.hydrate(<div>Hi</div>, container, () => {
259 - Scheduler.unstable_yieldValue('callback');
260 - });
261 - expect(container.textContent).toEqual('Hi');
262 - expect(Scheduler).toHaveYielded(['callback']);
263 - });
264 -
256 it('warns when unmounting with legacy API (no previous content)', () => {
257 const root = ReactDOMClient.createRoot(container);
258 root.render(<div>Hi</div>);
packages/react-dom/src/client/ReactDOMLegacy.js
+40 -79
@@ -27,7 +27,6 @@ import {
27
28 import {
29 createContainer,
30 - createHydrationContainer,
30 findHostInstanceWithNoPortals,
31 updateContainer,
32 flushSync,
@@ -110,81 +109,34 @@ function noopOnRecoverableError() {
109
110 function legacyCreateRootFromDOMContainer(
111 container: Container,
113 - initialChildren: ReactNodeList,
114 - parentComponent: ?React$Component<any, any>,
115 - callback: ?Function,
116 - isHydrationContainer: boolean,
112 + forceHydrate: boolean,
113 ): FiberRoot {
118 - if (isHydrationContainer) {
119 - if (typeof callback === 'function') {
120 - const originalCallback = callback;
121 - callback = function() {
122 - const instance = getPublicRootInstance(root);
123 - originalCallback.call(instance);
124 - };
125 - }
126 -
127 - const root = createHydrationContainer(
128 - initialChildren,
129 - callback,
130 - container,
131 - LegacyRoot,
132 - null, // hydrationCallbacks
133 - false, // isStrictMode
134 - false, // concurrentUpdatesByDefaultOverride,
135 - '', // identifierPrefix
136 - noopOnRecoverableError,
137 - // TODO(luna) Support hydration later
138 - null,
139 - );
140 - container._reactRootContainer = root;
141 - markContainerAsRoot(root.current, container);
142 -
143 - const rootContainerElement =
144 - container.nodeType === COMMENT_NODE ? container.parentNode : container;
145 - listenToAllSupportedEvents(rootContainerElement);
146 -
147 - flushSync();
148 - return root;
149 - } else {
150 - // First clear any existing content.
114 + // First clear any existing content.
115 + if (!forceHydrate) {
116 let rootSibling;
117 while ((rootSibling = container.lastChild)) {
118 container.removeChild(rootSibling);
119 }
120 + }
121
156 - if (typeof callback === 'function') {
157 - const originalCallback = callback;
158 - callback = function() {
159 - const instance = getPublicRootInstance(root);
160 - originalCallback.call(instance);
161 - };
162 - }
163 -
164 - const root = createContainer(
165 - container,
166 - LegacyRoot,
167 - null, // hydrationCallbacks
168 - false, // isStrictMode
169 - false, // concurrentUpdatesByDefaultOverride,
170 - '', // identifierPrefix
171 - noopOnRecoverableError, // onRecoverableError
172 - null, // transitionCallbacks
173 - );
174 - container._reactRootContainer = root;
175 - markContainerAsRoot(root.current, container);
176 -
177 - const rootContainerElement =
178 - container.nodeType === COMMENT_NODE ? container.parentNode : container;
179 - listenToAllSupportedEvents(rootContainerElement);
122 + const root = createContainer(
123 + container,
124 + LegacyRoot,
125 + forceHydrate,
126 + null, // hydrationCallbacks
127 + false, // isStrictMode
128 + false, // concurrentUpdatesByDefaultOverride,
129 + '', // identifierPrefix
130 + noopOnRecoverableError, // onRecoverableError
131 + null, // transitionCallbacks
132 + );
133 + markContainerAsRoot(root.current, container);
134
181 - // Initial mount should not be batched.
182 - flushSync(() => {
183 - updateContainer(initialChildren, root, parentComponent, callback);
184 - });
135 + const rootContainerElement =
136 + container.nodeType === COMMENT_NODE ? container.parentNode : container;
137 + listenToAllSupportedEvents(rootContainerElement);
138
186 - return root;
187 - }
139 + return root;
140 }
141
142 function warnOnInvalidCallback(callback: mixed, callerName: string): void {
@@ -212,30 +164,39 @@ function legacyRenderSubtreeIntoContainer(
164 warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
165 }
166
215 - const maybeRoot = container._reactRootContainer;
216 - let root: FiberRoot;
217 - if (!maybeRoot) {
167 + let root = container._reactRootContainer;
168 + let fiberRoot: FiberRoot;
169 + if (!root) {
170 // Initial mount
219 - root = legacyCreateRootFromDOMContainer(
171 + root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
172 container,
221 - children,
222 - parentComponent,
223 - callback,
173 forceHydrate,
174 );
175 + fiberRoot = root;
176 + if (typeof callback === 'function') {
177 + const originalCallback = callback;
178 + callback = function() {
179 + const instance = getPublicRootInstance(fiberRoot);
180 + originalCallback.call(instance);
181 + };
182 + }
183 + // Initial mount should not be batched.
184 + flushSync(() => {
185 + updateContainer(children, fiberRoot, parentComponent, callback);
186 + });
187 } else {
227 - root = maybeRoot;
188 + fiberRoot = root;
189 if (typeof callback === 'function') {
190 const originalCallback = callback;
191 callback = function() {
231 - const instance = getPublicRootInstance(root);
192 + const instance = getPublicRootInstance(fiberRoot);
193 originalCallback.call(instance);
194 };
195 }
196 // Update
236 - updateContainer(children, root, parentComponent, callback);
197 + updateContainer(children, fiberRoot, parentComponent, callback);
198 }
238 - return getPublicRootInstance(root);
199 + return getPublicRootInstance(fiberRoot);
200 }
201
202 export function findDOMNode(
packages/react-dom/src/client/ReactDOMRoot.js
+1 -1
@@ -225,6 +225,7 @@ export function createRoot(
225 const root = createContainer(
226 container,
227 ConcurrentRoot,
228 + false,
229 null,
230 isStrictMode,
231 concurrentUpdatesByDefaultOverride,
@@ -301,7 +302,6 @@ export function hydrateRoot(
302
303 const root = createHydrationContainer(
304 initialChildren,
304 - null,
305 container,
306 ConcurrentRoot,
307 hydrationCallbacks,
packages/react-dom/src/events/ReactDOMEventListener.js
+1 -2
@@ -53,7 +53,6 @@ import {
53 setCurrentUpdatePriority,
54 } from 'react-reconciler/src/ReactEventPriorities';
55 import ReactSharedInternals from 'shared/ReactSharedInternals';
56 -import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
56
57 const {ReactCurrentBatchConfig} = ReactSharedInternals;
58
@@ -387,7 +386,7 @@ export function findInstanceBlockingEvent(
386 targetInst = null;
387 } else if (tag === HostRoot) {
388 const root: FiberRoot = nearestMounted.stateNode;
390 - if (isRootDehydrated(root)) {
389 + if (root.isDehydrated) {
390 // If this happens during a replay something went wrong and it might block
391 // the whole system.
392 return getContainerFromFiber(nearestMounted);
packages/react-dom/src/events/ReactDOMEventReplaying.js
+1 -2
@@ -39,7 +39,6 @@ import {
39 } from '../client/ReactDOMComponentTree';
40 import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
41 import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities';
42 -import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
42
43 let _attemptSynchronousHydration: (fiber: Object) => void;
44
@@ -415,7 +414,7 @@ function attemptExplicitHydrationTarget(
414 }
415 } else if (tag === HostRoot) {
416 const root: FiberRoot = nearestMounted.stateNode;
418 - if (isRootDehydrated(root)) {
417 + if (root.isDehydrated) {
418 queuedTarget.blockedOn = getContainerFromFiber(nearestMounted);
419 // We don't currently have a way to increase the priority of
420 // a root other than sync.
packages/react-native-renderer/src/ReactFabric.js
+1
@@ -215,6 +215,7 @@ function render(
215 root = createContainer(
216 containerTag,
217 concurrentRoot ? ConcurrentRoot : LegacyRoot,
218 + false,
219 null,
220 false,
221 null,
packages/react-native-renderer/src/ReactNativeRenderer.js
+1
@@ -211,6 +211,7 @@ function render(
211 root = createContainer(
212 containerTag,
213 LegacyRoot,
214 + false,
215 null,
216 false,
217 null,
packages/react-noop-renderer/src/createReactNoop.js
+3
@@ -974,6 +974,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
974 root = NoopRenderer.createContainer(
975 container,
976 tag,
977 + false,
978 null,
979 null,
980 false,
@@ -995,6 +996,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
996 const fiberRoot = NoopRenderer.createContainer(
997 container,
998 ConcurrentRoot,
999 + false,
1000 null,
1001 null,
1002 false,
@@ -1027,6 +1029,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1029 const fiberRoot = NoopRenderer.createContainer(
1030 container,
1031 LegacyRoot,
1032 + false,
1033 null,
1034 null,
1035 false,
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+44 -115
@@ -7,11 +7,7 @@
7 * @flow
8 */
9
10 -import type {
11 - ReactProviderType,
12 - ReactContext,
13 - ReactNodeList,
14 -} from 'shared/ReactTypes';
10 +import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
11 import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
12 import type {Fiber, FiberRoot} from './ReactInternalTypes';
13 import type {TypeOfMode} from './ReactTypeOfMode';
@@ -33,7 +29,6 @@ import type {
29 SpawnedCachePool,
30 } from './ReactFiberCacheComponent.new';
31 import type {UpdateQueue} from './ReactUpdateQueue.new';
36 -import type {RootState} from './ReactFiberRoot.new';
32 import {
33 enableSuspenseAvoidThisFallback,
34 enableCPUSuspense,
@@ -1316,7 +1311,7 @@ function pushHostRootContext(workInProgress) {
1311
1312 function updateHostRoot(current, workInProgress, renderLanes) {
1313 pushHostRootContext(workInProgress);
1319 - const updateQueue: UpdateQueue<RootState> = (workInProgress.updateQueue: any);
1314 + const updateQueue = workInProgress.updateQueue;
1315
1316 if (current === null || updateQueue === null) {
1317 throw new Error(
@@ -1331,7 +1326,7 @@ function updateHostRoot(current, workInProgress, renderLanes) {
1326 const prevChildren = prevState.element;
1327 cloneUpdateQueue(current, workInProgress);
1328 processUpdateQueue(workInProgress, nextProps, null, renderLanes);
1334 - const nextState: RootState = workInProgress.memoizedState;
1329 + const nextState = workInProgress.memoizedState;
1330
1331 const root: FiberRoot = workInProgress.stateNode;
1332
@@ -1346,130 +1341,64 @@ function updateHostRoot(current, workInProgress, renderLanes) {
1341 }
1342
1343 if (enableTransitionTracing) {
1349 - // FIXME: Slipped past code review. This is not a safe mutation:
1350 - // workInProgress.memoizedState is a shared object. Need to fix before
1351 - // rolling out the Transition Tracing experiment.
1344 workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
1345 }
1346
1347 // Caution: React DevTools currently depends on this property
1348 // being called "element".
1349 const nextChildren = nextState.element;
1358 - if (supportsHydration && prevState.isDehydrated) {
1359 - // This is a hydration root whose shell has not yet hydrated. We should
1360 - // attempt to hydrate.
1361 - if (workInProgress.flags & ForceClientRender) {
1362 - // Something errored during a previous attempt to hydrate the shell, so we
1363 - // forced a client render.
1364 - const recoverableError = new Error(
1365 - 'There was an error while hydrating. Because the error happened outside ' +
1366 - 'of a Suspense boundary, the entire root will switch to ' +
1367 - 'client rendering.',
1368 - );
1369 - return mountHostRootWithoutHydrating(
1370 - current,
1371 - workInProgress,
1372 - updateQueue,
1373 - nextState,
1374 - nextChildren,
1375 - renderLanes,
1376 - recoverableError,
1377 - );
1378 - } else if (nextChildren !== prevChildren) {
1379 - const recoverableError = new Error(
1380 - 'This root received an early update, before anything was able ' +
1381 - 'hydrate. Switched the entire root to client rendering.',
1382 - );
1383 - return mountHostRootWithoutHydrating(
1384 - current,
1385 - workInProgress,
1386 - updateQueue,
1387 - nextState,
1388 - nextChildren,
1389 - renderLanes,
1390 - recoverableError,
1391 - );
1392 - } else {
1393 - // The outermost shell has not hydrated yet. Start hydrating.
1394 - enterHydrationState(workInProgress);
1395 - if (supportsHydration) {
1396 - const mutableSourceEagerHydrationData =
1397 - root.mutableSourceEagerHydrationData;
1398 - if (mutableSourceEagerHydrationData != null) {
1399 - for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
1400 - const mutableSource = ((mutableSourceEagerHydrationData[
1401 - i
1402 - ]: any): MutableSource<any>);
1403 - const version = mutableSourceEagerHydrationData[i + 1];
1404 - setWorkInProgressVersion(mutableSource, version);
1405 - }
1350 + if (nextChildren === prevChildren) {
1351 + resetHydrationState();
1352 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
1353 + }
1354 + if (root.isDehydrated && enterHydrationState(workInProgress)) {
1355 + // If we don't have any current children this might be the first pass.
1356 + // We always try to hydrate. If this isn't a hydration pass there won't
1357 + // be any children to hydrate which is effectively the same thing as
1358 + // not hydrating.
1359 +
1360 + if (supportsHydration) {
1361 + const mutableSourceEagerHydrationData =
1362 + root.mutableSourceEagerHydrationData;
1363 + if (mutableSourceEagerHydrationData != null) {
1364 + for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
1365 + const mutableSource = ((mutableSourceEagerHydrationData[
1366 + i
1367 + ]: any): MutableSource<any>);
1368 + const version = mutableSourceEagerHydrationData[i + 1];
1369 + setWorkInProgressVersion(mutableSource, version);
1370 }
1371 }
1372 + }
1373
1409 - const child = mountChildFibers(
1410 - workInProgress,
1411 - null,
1412 - nextChildren,
1413 - renderLanes,
1414 - );
1415 - workInProgress.child = child;
1374 + const child = mountChildFibers(
1375 + workInProgress,
1376 + null,
1377 + nextChildren,
1378 + renderLanes,
1379 + );
1380 + workInProgress.child = child;
1381
1417 - let node = child;
1418 - while (node) {
1419 - // Mark each child as hydrating. This is a fast path to know whether this
1420 - // tree is part of a hydrating tree. This is used to determine if a child
1421 - // node has fully mounted yet, and for scheduling event replaying.
1422 - // Conceptually this is similar to Placement in that a new subtree is
1423 - // inserted into the React tree here. It just happens to not need DOM
1424 - // mutations because it already exists.
1425 - node.flags = (node.flags & ~Placement) | Hydrating;
1426 - node = node.sibling;
1427 - }
1382 + let node = child;
1383 + while (node) {
1384 + // Mark each child as hydrating. This is a fast path to know whether this
1385 + // tree is part of a hydrating tree. This is used to determine if a child
1386 + // node has fully mounted yet, and for scheduling event replaying.
1387 + // Conceptually this is similar to Placement in that a new subtree is
1388 + // inserted into the React tree here. It just happens to not need DOM
1389 + // mutations because it already exists.
1390 + node.flags = (node.flags & ~Placement) | Hydrating;
1391 + node = node.sibling;
1392 }
1393 } else {
1430 - // Root is not dehydrated. Either this is a client-only root, or it
1431 - // already hydrated.
1432 - resetHydrationState();
1433 - if (nextChildren === prevChildren) {
1434 - return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
1435 - }
1394 + // Otherwise reset hydration state in case we aborted and resumed another
1395 + // root.
1396 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1397 + resetHydrationState();
1398 }
1399 return workInProgress.child;
1400 }
1401
1441 -function mountHostRootWithoutHydrating(
1442 - current: Fiber,
1443 - workInProgress: Fiber,
1444 - updateQueue: UpdateQueue<RootState>,
1445 - nextState: RootState,
1446 - nextChildren: ReactNodeList,
1447 - renderLanes: Lanes,
1448 - recoverableError: Error,
1449 -) {
1450 - // Revert to client rendering.
1451 - resetHydrationState();
1452 -
1453 - queueHydrationError(recoverableError);
1454 -
1455 - workInProgress.flags |= ForceClientRender;
1456 -
1457 - // Flip isDehydrated to false to indicate that when this render
1458 - // finishes, the root will no longer be dehydrated.
1459 - const overrideState: RootState = {
1460 - element: nextChildren,
1461 - isDehydrated: false,
1462 - cache: nextState.cache,
1463 - transitions: nextState.transitions,
1464 - };
1465 - // `baseState` can always be the last state because the root doesn't
1466 - // have reducer functions so it doesn't need rebasing.
1467 - updateQueue.baseState = overrideState;
1468 - workInProgress.memoizedState = overrideState;
1469 - reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1470 - return workInProgress.child;
1471 -}
1472 -
1402 function updateHostComponent(
1403 current: Fiber | null,
1404 workInProgress: Fiber,
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+44 -115
@@ -7,11 +7,7 @@
7 * @flow
8 */
9
10 -import type {
11 - ReactProviderType,
12 - ReactContext,
13 - ReactNodeList,
14 -} from 'shared/ReactTypes';
10 +import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
11 import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
12 import type {Fiber, FiberRoot} from './ReactInternalTypes';
13 import type {TypeOfMode} from './ReactTypeOfMode';
@@ -33,7 +29,6 @@ import type {
29 SpawnedCachePool,
30 } from './ReactFiberCacheComponent.old';
31 import type {UpdateQueue} from './ReactUpdateQueue.old';
36 -import type {RootState} from './ReactFiberRoot.old';
32 import {
33 enableSuspenseAvoidThisFallback,
34 enableCPUSuspense,
@@ -1316,7 +1311,7 @@ function pushHostRootContext(workInProgress) {
1311
1312 function updateHostRoot(current, workInProgress, renderLanes) {
1313 pushHostRootContext(workInProgress);
1319 - const updateQueue: UpdateQueue<RootState> = (workInProgress.updateQueue: any);
1314 + const updateQueue = workInProgress.updateQueue;
1315
1316 if (current === null || updateQueue === null) {
1317 throw new Error(
@@ -1331,7 +1326,7 @@ function updateHostRoot(current, workInProgress, renderLanes) {
1326 const prevChildren = prevState.element;
1327 cloneUpdateQueue(current, workInProgress);
1328 processUpdateQueue(workInProgress, nextProps, null, renderLanes);
1334 - const nextState: RootState = workInProgress.memoizedState;
1329 + const nextState = workInProgress.memoizedState;
1330
1331 const root: FiberRoot = workInProgress.stateNode;
1332
@@ -1346,130 +1341,64 @@ function updateHostRoot(current, workInProgress, renderLanes) {
1341 }
1342
1343 if (enableTransitionTracing) {
1349 - // FIXME: Slipped past code review. This is not a safe mutation:
1350 - // workInProgress.memoizedState is a shared object. Need to fix before
1351 - // rolling out the Transition Tracing experiment.
1344 workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
1345 }
1346
1347 // Caution: React DevTools currently depends on this property
1348 // being called "element".
1349 const nextChildren = nextState.element;
1358 - if (supportsHydration && prevState.isDehydrated) {
1359 - // This is a hydration root whose shell has not yet hydrated. We should
1360 - // attempt to hydrate.
1361 - if (workInProgress.flags & ForceClientRender) {
1362 - // Something errored during a previous attempt to hydrate the shell, so we
1363 - // forced a client render.
1364 - const recoverableError = new Error(
1365 - 'There was an error while hydrating. Because the error happened outside ' +
1366 - 'of a Suspense boundary, the entire root will switch to ' +
1367 - 'client rendering.',
1368 - );
1369 - return mountHostRootWithoutHydrating(
1370 - current,
1371 - workInProgress,
1372 - updateQueue,
1373 - nextState,
1374 - nextChildren,
1375 - renderLanes,
1376 - recoverableError,
1377 - );
1378 - } else if (nextChildren !== prevChildren) {
1379 - const recoverableError = new Error(
1380 - 'This root received an early update, before anything was able ' +
1381 - 'hydrate. Switched the entire root to client rendering.',
1382 - );
1383 - return mountHostRootWithoutHydrating(
1384 - current,
1385 - workInProgress,
1386 - updateQueue,
1387 - nextState,
1388 - nextChildren,
1389 - renderLanes,
1390 - recoverableError,
1391 - );
1392 - } else {
1393 - // The outermost shell has not hydrated yet. Start hydrating.
1394 - enterHydrationState(workInProgress);
1395 - if (supportsHydration) {
1396 - const mutableSourceEagerHydrationData =
1397 - root.mutableSourceEagerHydrationData;
1398 - if (mutableSourceEagerHydrationData != null) {
1399 - for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
1400 - const mutableSource = ((mutableSourceEagerHydrationData[
1401 - i
1402 - ]: any): MutableSource<any>);
1403 - const version = mutableSourceEagerHydrationData[i + 1];
1404 - setWorkInProgressVersion(mutableSource, version);
1405 - }
1350 + if (nextChildren === prevChildren) {
1351 + resetHydrationState();
1352 + return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
1353 + }
1354 + if (root.isDehydrated && enterHydrationState(workInProgress)) {
1355 + // If we don't have any current children this might be the first pass.
1356 + // We always try to hydrate. If this isn't a hydration pass there won't
1357 + // be any children to hydrate which is effectively the same thing as
1358 + // not hydrating.
1359 +
1360 + if (supportsHydration) {
1361 + const mutableSourceEagerHydrationData =
1362 + root.mutableSourceEagerHydrationData;
1363 + if (mutableSourceEagerHydrationData != null) {
1364 + for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
1365 + const mutableSource = ((mutableSourceEagerHydrationData[
1366 + i
1367 + ]: any): MutableSource<any>);
1368 + const version = mutableSourceEagerHydrationData[i + 1];
1369 + setWorkInProgressVersion(mutableSource, version);
1370 }
1371 }
1372 + }
1373
1409 - const child = mountChildFibers(
1410 - workInProgress,
1411 - null,
1412 - nextChildren,
1413 - renderLanes,
1414 - );
1415 - workInProgress.child = child;
1374 + const child = mountChildFibers(
1375 + workInProgress,
1376 + null,
1377 + nextChildren,
1378 + renderLanes,
1379 + );
1380 + workInProgress.child = child;
1381
1417 - let node = child;
1418 - while (node) {
1419 - // Mark each child as hydrating. This is a fast path to know whether this
1420 - // tree is part of a hydrating tree. This is used to determine if a child
1421 - // node has fully mounted yet, and for scheduling event replaying.
1422 - // Conceptually this is similar to Placement in that a new subtree is
1423 - // inserted into the React tree here. It just happens to not need DOM
1424 - // mutations because it already exists.
1425 - node.flags = (node.flags & ~Placement) | Hydrating;
1426 - node = node.sibling;
1427 - }
1382 + let node = child;
1383 + while (node) {
1384 + // Mark each child as hydrating. This is a fast path to know whether this
1385 + // tree is part of a hydrating tree. This is used to determine if a child
1386 + // node has fully mounted yet, and for scheduling event replaying.
1387 + // Conceptually this is similar to Placement in that a new subtree is
1388 + // inserted into the React tree here. It just happens to not need DOM
1389 + // mutations because it already exists.
1390 + node.flags = (node.flags & ~Placement) | Hydrating;
1391 + node = node.sibling;
1392 }
1393 } else {
1430 - // Root is not dehydrated. Either this is a client-only root, or it
1431 - // already hydrated.
1432 - resetHydrationState();
1433 - if (nextChildren === prevChildren) {
1434 - return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
1435 - }
1394 + // Otherwise reset hydration state in case we aborted and resumed another
1395 + // root.
1396 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1397 + resetHydrationState();
1398 }
1399 return workInProgress.child;
1400 }
1401
1441 -function mountHostRootWithoutHydrating(
1442 - current: Fiber,
1443 - workInProgress: Fiber,
1444 - updateQueue: UpdateQueue<RootState>,
1445 - nextState: RootState,
1446 - nextChildren: ReactNodeList,
1447 - renderLanes: Lanes,
1448 - recoverableError: Error,
1449 -) {
1450 - // Revert to client rendering.
1451 - resetHydrationState();
1452 -
1453 - queueHydrationError(recoverableError);
1454 -
1455 - workInProgress.flags |= ForceClientRender;
1456 -
1457 - // Flip isDehydrated to false to indicate that when this render
1458 - // finishes, the root will no longer be dehydrated.
1459 - const overrideState: RootState = {
1460 - element: nextChildren,
1461 - isDehydrated: false,
1462 - cache: nextState.cache,
1463 - transitions: nextState.transitions,
1464 - };
1465 - // `baseState` can always be the last state because the root doesn't
1466 - // have reducer functions so it doesn't need rebasing.
1467 - updateQueue.baseState = overrideState;
1468 - workInProgress.memoizedState = overrideState;
1469 - reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1470 - return workInProgress.child;
1471 -}
1472 -
1402 function updateHostComponent(
1403 current: Fiber | null,
1404 workInProgress: Fiber,
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+10 -13
@@ -25,7 +25,6 @@ import type {Wakeable} from 'shared/ReactTypes';
25 import type {OffscreenState} from './ReactFiberOffscreenComponent';
26 import type {HookFlags} from './ReactHookEffectTags';
27 import type {Cache} from './ReactFiberCacheComponent.new';
28 -import type {RootState} from './ReactFiberRoot.new';
28
29 import {
30 enableCreateEventHandleAPI,
@@ -1878,12 +1877,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1877 }
1878 case HostRoot: {
1879 if (supportsHydration) {
1881 - if (current !== null) {
1882 - const prevRootState: RootState = current.memoizedState;
1883 - if (prevRootState.isDehydrated) {
1884 - const root: FiberRoot = finishedWork.stateNode;
1885 - commitHydratedContainer(root.containerInfo);
1886 - }
1880 + const root: FiberRoot = finishedWork.stateNode;
1881 + if (root.isDehydrated) {
1882 + // We've just hydrated. No need to hydrate again.
1883 + root.isDehydrated = false;
1884 + commitHydratedContainer(root.containerInfo);
1885 }
1886 }
1887 break;
@@ -1987,12 +1985,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1985 }
1986 case HostRoot: {
1987 if (supportsHydration) {
1990 - if (current !== null) {
1991 - const prevRootState: RootState = current.memoizedState;
1992 - if (prevRootState.isDehydrated) {
1993 - const root: FiberRoot = finishedWork.stateNode;
1994 - commitHydratedContainer(root.containerInfo);
1995 - }
1988 + const root: FiberRoot = finishedWork.stateNode;
1989 + if (root.isDehydrated) {
1990 + // We've just hydrated. No need to hydrate again.
1991 + root.isDehydrated = false;
1992 + commitHydratedContainer(root.containerInfo);
1993 }
1994 }
1995 return;
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+10 -13
@@ -25,7 +25,6 @@ import type {Wakeable} from 'shared/ReactTypes';
25 import type {OffscreenState} from './ReactFiberOffscreenComponent';
26 import type {HookFlags} from './ReactHookEffectTags';
27 import type {Cache} from './ReactFiberCacheComponent.old';
28 -import type {RootState} from './ReactFiberRoot.old';
28
29 import {
30 enableCreateEventHandleAPI,
@@ -1878,12 +1877,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1877 }
1878 case HostRoot: {
1879 if (supportsHydration) {
1881 - if (current !== null) {
1882 - const prevRootState: RootState = current.memoizedState;
1883 - if (prevRootState.isDehydrated) {
1884 - const root: FiberRoot = finishedWork.stateNode;
1885 - commitHydratedContainer(root.containerInfo);
1886 - }
1880 + const root: FiberRoot = finishedWork.stateNode;
1881 + if (root.isDehydrated) {
1882 + // We've just hydrated. No need to hydrate again.
1883 + root.isDehydrated = false;
1884 + commitHydratedContainer(root.containerInfo);
1885 }
1886 }
1887 break;
@@ -1987,12 +1985,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1985 }
1986 case HostRoot: {
1987 if (supportsHydration) {
1990 - if (current !== null) {
1991 - const prevRootState: RootState = current.memoizedState;
1992 - if (prevRootState.isDehydrated) {
1993 - const root: FiberRoot = finishedWork.stateNode;
1994 - commitHydratedContainer(root.containerInfo);
1995 - }
1988 + const root: FiberRoot = finishedWork.stateNode;
1989 + if (root.isDehydrated) {
1990 + // We've just hydrated. No need to hydrate again.
1991 + root.isDehydrated = false;
1992 + commitHydratedContainer(root.containerInfo);
1993 }
1994 }
1995 return;
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+6 -24
@@ -8,7 +8,6 @@
8 */
9
10 import type {Fiber} from './ReactInternalTypes';
11 -import type {RootState} from './ReactFiberRoot.new';
11 import type {Lanes, Lane} from './ReactFiberLane.new';
12 import type {
13 ReactScopeInstance,
@@ -891,29 +890,12 @@ function completeWork(
890 // If we hydrated, then we'll need to schedule an update for
891 // the commit side-effects on the root.
892 markUpdate(workInProgress);
894 - } else {
895 - if (current !== null) {
896 - const prevState: RootState = current.memoizedState;
897 - if (
898 - // Check if this is a client root
899 - !prevState.isDehydrated ||
900 - // Check if we reverted to client rendering (e.g. due to an error)
901 - (workInProgress.flags & ForceClientRender) !== NoFlags
902 - ) {
903 - // Schedule an effect to clear this container at the start of the
904 - // next commit. This handles the case of React rendering into a
905 - // container with previous children. It's also safe to do for
906 - // updates too, because current.child would only be null if the
907 - // previous render was null (so the container would already
908 - // be empty).
909 - workInProgress.flags |= Snapshot;
910 -
911 - // If this was a forced client render, there may have been
912 - // recoverable errors during first hydration attempt. If so, add
913 - // them to a queue so we can log them in the commit phase.
914 - upgradeHydrationErrorsToRecoverable();
915 - }
916 - }
893 + } else if (!fiberRoot.isDehydrated) {
894 + // Schedule an effect to clear this container at the start of the next commit.
895 + // This handles the case of React rendering into a container with previous children.
896 + // It's also safe to do for updates too, because current.child would only be null
897 + // if the previous render was null (so the container would already be empty).
898 + workInProgress.flags |= Snapshot;
899 }
900 }
901 updateHostContainer(current, workInProgress);
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+6 -24
@@ -8,7 +8,6 @@
8 */
9
10 import type {Fiber} from './ReactInternalTypes';
11 -import type {RootState} from './ReactFiberRoot.old';
11 import type {Lanes, Lane} from './ReactFiberLane.old';
12 import type {
13 ReactScopeInstance,
@@ -891,29 +890,12 @@ function completeWork(
890 // If we hydrated, then we'll need to schedule an update for
891 // the commit side-effects on the root.
892 markUpdate(workInProgress);
894 - } else {
895 - if (current !== null) {
896 - const prevState: RootState = current.memoizedState;
897 - if (
898 - // Check if this is a client root
899 - !prevState.isDehydrated ||
900 - // Check if we reverted to client rendering (e.g. due to an error)
901 - (workInProgress.flags & ForceClientRender) !== NoFlags
902 - ) {
903 - // Schedule an effect to clear this container at the start of the
904 - // next commit. This handles the case of React rendering into a
905 - // container with previous children. It's also safe to do for
906 - // updates too, because current.child would only be null if the
907 - // previous render was null (so the container would already
908 - // be empty).
909 - workInProgress.flags |= Snapshot;
910 -
911 - // If this was a forced client render, there may have been
912 - // recoverable errors during first hydration attempt. If so, add
913 - // them to a queue so we can log them in the commit phase.
914 - upgradeHydrationErrorsToRecoverable();
915 - }
916 - }
893 + } else if (!fiberRoot.isDehydrated) {
894 + // Schedule an effect to clear this container at the start of the next commit.
895 + // This handles the case of React rendering into a container with previous children.
896 + // It's also safe to do for updates too, because current.child would only be null
897 + // if the previous render was null (so the container would already be empty).
898 + workInProgress.flags |= Snapshot;
899 }
900 }
901 updateHostContainer(current, workInProgress);
packages/react-reconciler/src/ReactFiberReconciler.new.js
+7 -11
@@ -48,7 +48,6 @@ import {
48 isContextProvider as isLegacyContextProvider,
49 } from './ReactFiberContext.new';
50 import {createFiberRoot} from './ReactFiberRoot.new';
51 -import {isRootDehydrated} from './ReactFiberShellHydration';
51 import {
52 injectInternals,
53 markRenderScheduled,
@@ -246,6 +245,9 @@ function findHostInstanceWithWarning(
245 export function createContainer(
246 containerInfo: Container,
247 tag: RootTag,
248 + // TODO: We can remove hydration-specific stuff from createContainer once
249 + // we delete legacy mode. The new root API uses createHydrationContainer.
250 + hydrate: boolean,
251 hydrationCallbacks: null | SuspenseHydrationCallbacks,
252 isStrictMode: boolean,
253 concurrentUpdatesByDefaultOverride: null | boolean,
@@ -253,13 +255,10 @@ export function createContainer(
255 onRecoverableError: (error: mixed) => void,
256 transitionCallbacks: null | TransitionTracingCallbacks,
257 ): OpaqueRoot {
256 - const hydrate = false;
257 - const initialChildren = null;
258 return createFiberRoot(
259 containerInfo,
260 tag,
261 hydrate,
262 - initialChildren,
262 hydrationCallbacks,
263 isStrictMode,
264 concurrentUpdatesByDefaultOverride,
@@ -271,8 +270,6 @@ export function createContainer(
270
271 export function createHydrationContainer(
272 initialChildren: ReactNodeList,
274 - // TODO: Remove `callback` when we delete legacy mode.
275 - callback: ?Function,
273 containerInfo: Container,
274 tag: RootTag,
275 hydrationCallbacks: null | SuspenseHydrationCallbacks,
@@ -287,7 +284,6 @@ export function createHydrationContainer(
284 containerInfo,
285 tag,
286 hydrate,
290 - initialChildren,
287 hydrationCallbacks,
288 isStrictMode,
289 concurrentUpdatesByDefaultOverride,
@@ -306,9 +302,9 @@ export function createHydrationContainer(
302 const eventTime = requestEventTime();
303 const lane = requestUpdateLane(current);
304 const update = createUpdate(eventTime, lane);
309 - update.payload = {isDehydrated: false};
310 - update.callback =
311 - callback !== undefined && callback !== null ? callback : null;
305 + // Caution: React DevTools currently depends on this property
306 + // being called "element".
307 + update.payload = {element: initialChildren};
308 enqueueUpdate(current, update, lane);
309 scheduleInitialHydrationOnRoot(root, lane, eventTime);
310
@@ -413,7 +409,7 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
409 switch (fiber.tag) {
410 case HostRoot:
411 const root: FiberRoot = fiber.stateNode;
416 - if (isRootDehydrated(root)) {
412 + if (root.isDehydrated) {
413 // Flush the first scheduled "update".
414 const lanes = getHighestPriorityPendingLanes(root);
415 flushRoot(root, lanes);
packages/react-reconciler/src/ReactFiberReconciler.old.js
+7 -11
@@ -48,7 +48,6 @@ import {
48 isContextProvider as isLegacyContextProvider,
49 } from './ReactFiberContext.old';
50 import {createFiberRoot} from './ReactFiberRoot.old';
51 -import {isRootDehydrated} from './ReactFiberShellHydration';
51 import {
52 injectInternals,
53 markRenderScheduled,
@@ -246,6 +245,9 @@ function findHostInstanceWithWarning(
245 export function createContainer(
246 containerInfo: Container,
247 tag: RootTag,
248 + // TODO: We can remove hydration-specific stuff from createContainer once
249 + // we delete legacy mode. The new root API uses createHydrationContainer.
250 + hydrate: boolean,
251 hydrationCallbacks: null | SuspenseHydrationCallbacks,
252 isStrictMode: boolean,
253 concurrentUpdatesByDefaultOverride: null | boolean,
@@ -253,13 +255,10 @@ export function createContainer(
255 onRecoverableError: (error: mixed) => void,
256 transitionCallbacks: null | TransitionTracingCallbacks,
257 ): OpaqueRoot {
256 - const hydrate = false;
257 - const initialChildren = null;
258 return createFiberRoot(
259 containerInfo,
260 tag,
261 hydrate,
262 - initialChildren,
262 hydrationCallbacks,
263 isStrictMode,
264 concurrentUpdatesByDefaultOverride,
@@ -271,8 +270,6 @@ export function createContainer(
270
271 export function createHydrationContainer(
272 initialChildren: ReactNodeList,
274 - // TODO: Remove `callback` when we delete legacy mode.
275 - callback: ?Function,
273 containerInfo: Container,
274 tag: RootTag,
275 hydrationCallbacks: null | SuspenseHydrationCallbacks,
@@ -287,7 +284,6 @@ export function createHydrationContainer(
284 containerInfo,
285 tag,
286 hydrate,
290 - initialChildren,
287 hydrationCallbacks,
288 isStrictMode,
289 concurrentUpdatesByDefaultOverride,
@@ -306,9 +302,9 @@ export function createHydrationContainer(
302 const eventTime = requestEventTime();
303 const lane = requestUpdateLane(current);
304 const update = createUpdate(eventTime, lane);
309 - update.payload = {isDehydrated: false};
310 - update.callback =
311 - callback !== undefined && callback !== null ? callback : null;
305 + // Caution: React DevTools currently depends on this property
306 + // being called "element".
307 + update.payload = {element: initialChildren};
308 enqueueUpdate(current, update, lane);
309 scheduleInitialHydrationOnRoot(root, lane, eventTime);
310
@@ -413,7 +409,7 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
409 switch (fiber.tag) {
410 case HostRoot:
411 const root: FiberRoot = fiber.stateNode;
416 - if (isRootDehydrated(root)) {
412 + if (root.isDehydrated) {
413 // Flush the first scheduled "update".
414 const lanes = getHighestPriorityPendingLanes(root);
415 flushRoot(root, lanes);
packages/react-reconciler/src/ReactFiberRoot.new.js
+5 -9
@@ -7,7 +7,6 @@
7 * @flow
8 */
9
10 -import type {ReactNodeList} from 'shared/ReactTypes';
10 import type {
11 FiberRoot,
12 SuspenseHydrationCallbacks,
@@ -40,8 +39,7 @@ import {createCache, retainCache} from './ReactFiberCacheComponent.new';
39
40 export type RootState = {
41 element: any,
43 - isDehydrated: boolean,
44 - cache: Cache,
42 + cache: Cache | null,
43 transitions: Transitions | null,
44 };
45
@@ -61,6 +59,7 @@ function FiberRootNode(
59 this.timeoutHandle = noTimeout;
60 this.context = null;
61 this.pendingContext = null;
62 + this.isDehydrated = hydrate;
63 this.callbackNode = null;
64 this.callbackPriority = NoLane;
65 this.eventTimes = createLaneMap(NoLanes);
@@ -129,7 +128,6 @@ export function createFiberRoot(
128 containerInfo: any,
129 tag: RootTag,
130 hydrate: boolean,
132 - initialChildren: ReactNodeList,
131 hydrationCallbacks: null | SuspenseHydrationCallbacks,
132 isStrictMode: boolean,
133 concurrentUpdatesByDefaultOverride: null | boolean,
@@ -180,17 +178,15 @@ export function createFiberRoot(
178 root.pooledCache = initialCache;
179 retainCache(initialCache);
180 const initialState: RootState = {
183 - element: initialChildren,
184 - isDehydrated: hydrate,
181 + element: null,
182 cache: initialCache,
183 transitions: null,
184 };
185 uninitializedFiber.memoizedState = initialState;
186 } else {
187 const initialState: RootState = {
191 - element: initialChildren,
192 - isDehydrated: hydrate,
193 - cache: (null: any), // not enabled yet
188 + element: null,
189 + cache: null,
190 transitions: null,
191 };
192 uninitializedFiber.memoizedState = initialState;
packages/react-reconciler/src/ReactFiberRoot.old.js
+5 -9
@@ -7,7 +7,6 @@
7 * @flow
8 */
9
10 -import type {ReactNodeList} from 'shared/ReactTypes';
10 import type {
11 FiberRoot,
12 SuspenseHydrationCallbacks,
@@ -40,8 +39,7 @@ import {createCache, retainCache} from './ReactFiberCacheComponent.old';
39
40 export type RootState = {
41 element: any,
43 - isDehydrated: boolean,
44 - cache: Cache,
42 + cache: Cache | null,
43 transitions: Transitions | null,
44 };
45
@@ -61,6 +59,7 @@ function FiberRootNode(
59 this.timeoutHandle = noTimeout;
60 this.context = null;
61 this.pendingContext = null;
62 + this.isDehydrated = hydrate;
63 this.callbackNode = null;
64 this.callbackPriority = NoLane;
65 this.eventTimes = createLaneMap(NoLanes);
@@ -129,7 +128,6 @@ export function createFiberRoot(
128 containerInfo: any,
129 tag: RootTag,
130 hydrate: boolean,
132 - initialChildren: ReactNodeList,
131 hydrationCallbacks: null | SuspenseHydrationCallbacks,
132 isStrictMode: boolean,
133 concurrentUpdatesByDefaultOverride: null | boolean,
@@ -180,17 +178,15 @@ export function createFiberRoot(
178 root.pooledCache = initialCache;
179 retainCache(initialCache);
180 const initialState: RootState = {
183 - element: initialChildren,
184 - isDehydrated: hydrate,
181 + element: null,
182 cache: initialCache,
183 transitions: null,
184 };
185 uninitializedFiber.memoizedState = initialState;
186 } else {
187 const initialState: RootState = {
191 - element: initialChildren,
192 - isDehydrated: hydrate,
193 - cache: (null: any), // not enabled yet
188 + element: null,
189 + cache: null,
190 transitions: null,
191 };
192 uninitializedFiber.memoizedState = initialState;
packages/react-reconciler/src/ReactFiberShellHydration.js deleted
-19
@@ -1,19 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {FiberRoot} from './ReactInternalTypes';
11 -import type {RootState} from './ReactFiberRoot.new';
12 -
13 -// This is imported by the event replaying implementation in React DOM. It's
14 -// in a separate file to break a circular dependency between the renderer and
15 -// the reconciler.
16 -export function isRootDehydrated(root: FiberRoot) {
17 - const currentState: RootState = root.current.memoizedState;
18 - return currentState.isDehydrated;
19 -}
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+44 -36
@@ -88,7 +88,6 @@ import {
88 createWorkInProgress,
89 assignFiberPropertiesInDEV,
90 } from './ReactFiber.new';
91 -import {isRootDehydrated} from './ReactFiberShellHydration';
91 import {NoMode, ProfileMode, ConcurrentMode} from './ReactTypeOfMode';
92 import {
93 HostRoot,
@@ -110,7 +109,6 @@ import {
109 StoreConsistency,
110 HostEffectMask,
111 Hydrating,
113 - ForceClientRender,
112 BeforeMutationMask,
113 MutationMask,
114 LayoutMask,
@@ -583,7 +581,34 @@ export function scheduleUpdateOnFiber(
581 }
582 }
583
586 - if (root === workInProgressRoot) {
584 + if (root.isDehydrated && root.tag !== LegacyRoot) {
585 + // This root's shell hasn't hydrated yet. Revert to client rendering.
586 + if (workInProgressRoot === root) {
587 + // If this happened during an interleaved event, interrupt the
588 + // in-progress hydration. Theoretically, we could attempt to force a
589 + // synchronous hydration before switching to client rendering, but the
590 + // most common reason the shell hasn't hydrated yet is because it
591 + // suspended. So it's very likely to suspend again anyway. For
592 + // simplicity, we'll skip that atttempt and go straight to
593 + // client rendering.
594 + //
595 + // Another way to model this would be to give the initial hydration its
596 + // own special lane. However, it may not be worth adding a lane solely
597 + // for this purpose, so we'll wait until we find another use case before
598 + // adding it.
599 + //
600 + // TODO: Consider only interrupting hydration if the priority of the
601 + // update is higher than default.
602 + prepareFreshStack(root, NoLanes);
603 + }
604 + root.isDehydrated = false;
605 + const error = new Error(
606 + 'This root received an early update, before anything was able ' +
607 + 'hydrate. Switched the entire root to client rendering.',
608 + );
609 + const onRecoverableError = root.onRecoverableError;
610 + onRecoverableError(error);
611 + } else if (root === workInProgressRoot) {
612 // TODO: Consolidate with `isInterleavedUpdate` check
613
614 // Received an update to a tree that's in the middle of rendering. Mark
@@ -991,42 +1016,28 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
1016 function recoverFromConcurrentError(root, errorRetryLanes) {
1017 // If an error occurred during hydration, discard server response and fall
1018 // back to client side render.
994 -
995 - // Before rendering again, save the errors from the previous attempt.
996 - const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
997 -
998 - if (isRootDehydrated(root)) {
999 - // The shell failed to hydrate. Set a flag to force a client rendering
1000 - // during the next attempt. To do this, we call prepareFreshStack now
1001 - // to create the root work-in-progress fiber. This is a bit weird in terms
1002 - // of factoring, because it relies on renderRootSync not calling
1003 - // prepareFreshStack again in the call below, which happens because the
1004 - // root and lanes haven't changed.
1005 - //
1006 - // TODO: I think what we should do is set ForceClientRender inside
1007 - // throwException, like we do for nested Suspense boundaries. The reason
1008 - // it's here instead is so we can switch to the synchronous work loop, too.
1009 - // Something to consider for a future refactor.
1010 - const rootWorkInProgress = prepareFreshStack(root, errorRetryLanes);
1011 - rootWorkInProgress.flags |= ForceClientRender;
1019 + if (root.isDehydrated) {
1020 + root.isDehydrated = false;
1021 if (__DEV__) {
1022 errorHydratingContainer(root.containerInfo);
1023 }
1024 + const error = new Error(
1025 + 'There was an error while hydrating. Because the error happened outside ' +
1026 + 'of a Suspense boundary, the entire root will switch to ' +
1027 + 'client rendering.',
1028 + );
1029 + renderDidError(error);
1030 }
1031
1032 + const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
1033 const exitStatus = renderRootSync(root, errorRetryLanes);
1034 if (exitStatus !== RootErrored) {
1035 // Successfully finished rendering on retry
1020 -
1021 - // The errors from the failed first attempt have been recovered. Add
1022 - // them to the collection of recoverable errors. We'll log them in the
1023 - // commit phase.
1024 - const errorsFromSecondAttempt = workInProgressRootRecoverableErrors;
1025 - workInProgressRootRecoverableErrors = errorsFromFirstAttempt;
1026 - // The errors from the second attempt should be queued after the errors
1027 - // from the first attempt, to preserve the causal sequence.
1028 - if (errorsFromSecondAttempt !== null) {
1029 - queueRecoverableErrors(errorsFromSecondAttempt);
1036 + if (errorsFromFirstAttempt !== null) {
1037 + // The errors from the failed first attempt have been recovered. Add
1038 + // them to the collection of recoverable errors. We'll log them in the
1039 + // commit phase.
1040 + queueRecoverableErrors(errorsFromFirstAttempt);
1041 }
1042 } else {
1043 // The UI failed to recover.
@@ -1442,7 +1453,7 @@ export function popRenderLanes(fiber: Fiber) {
1453 popFromStack(subtreeRenderLanesCursor, fiber);
1454 }
1455
1445 -function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1456 +function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
1457 root.finishedWork = null;
1458 root.finishedLanes = NoLanes;
1459
@@ -1468,8 +1479,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1479 }
1480 }
1481 workInProgressRoot = root;
1471 - const rootWorkInProgress = createWorkInProgress(root.current, null);
1472 - workInProgress = rootWorkInProgress;
1482 + workInProgress = createWorkInProgress(root.current, null);
1483 workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1484 workInProgressRootExitStatus = RootInProgress;
1485 workInProgressRootFatalError = null;
@@ -1485,8 +1495,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1495 if (__DEV__) {
1496 ReactStrictModeWarnings.discardPendingWarnings();
1497 }
1488 -
1489 - return rootWorkInProgress;
1498 }
1499
1500 function handleError(root, thrownValue): void {
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+44 -36
@@ -88,7 +88,6 @@ import {
88 createWorkInProgress,
89 assignFiberPropertiesInDEV,
90 } from './ReactFiber.old';
91 -import {isRootDehydrated} from './ReactFiberShellHydration';
91 import {NoMode, ProfileMode, ConcurrentMode} from './ReactTypeOfMode';
92 import {
93 HostRoot,
@@ -110,7 +109,6 @@ import {
109 StoreConsistency,
110 HostEffectMask,
111 Hydrating,
113 - ForceClientRender,
112 BeforeMutationMask,
113 MutationMask,
114 LayoutMask,
@@ -583,7 +581,34 @@ export function scheduleUpdateOnFiber(
581 }
582 }
583
586 - if (root === workInProgressRoot) {
584 + if (root.isDehydrated && root.tag !== LegacyRoot) {
585 + // This root's shell hasn't hydrated yet. Revert to client rendering.
586 + if (workInProgressRoot === root) {
587 + // If this happened during an interleaved event, interrupt the
588 + // in-progress hydration. Theoretically, we could attempt to force a
589 + // synchronous hydration before switching to client rendering, but the
590 + // most common reason the shell hasn't hydrated yet is because it
591 + // suspended. So it's very likely to suspend again anyway. For
592 + // simplicity, we'll skip that atttempt and go straight to
593 + // client rendering.
594 + //
595 + // Another way to model this would be to give the initial hydration its
596 + // own special lane. However, it may not be worth adding a lane solely
597 + // for this purpose, so we'll wait until we find another use case before
598 + // adding it.
599 + //
600 + // TODO: Consider only interrupting hydration if the priority of the
601 + // update is higher than default.
602 + prepareFreshStack(root, NoLanes);
603 + }
604 + root.isDehydrated = false;
605 + const error = new Error(
606 + 'This root received an early update, before anything was able ' +
607 + 'hydrate. Switched the entire root to client rendering.',
608 + );
609 + const onRecoverableError = root.onRecoverableError;
610 + onRecoverableError(error);
611 + } else if (root === workInProgressRoot) {
612 // TODO: Consolidate with `isInterleavedUpdate` check
613
614 // Received an update to a tree that's in the middle of rendering. Mark
@@ -991,42 +1016,28 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
1016 function recoverFromConcurrentError(root, errorRetryLanes) {
1017 // If an error occurred during hydration, discard server response and fall
1018 // back to client side render.
994 -
995 - // Before rendering again, save the errors from the previous attempt.
996 - const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
997 -
998 - if (isRootDehydrated(root)) {
999 - // The shell failed to hydrate. Set a flag to force a client rendering
1000 - // during the next attempt. To do this, we call prepareFreshStack now
1001 - // to create the root work-in-progress fiber. This is a bit weird in terms
1002 - // of factoring, because it relies on renderRootSync not calling
1003 - // prepareFreshStack again in the call below, which happens because the
1004 - // root and lanes haven't changed.
1005 - //
1006 - // TODO: I think what we should do is set ForceClientRender inside
1007 - // throwException, like we do for nested Suspense boundaries. The reason
1008 - // it's here instead is so we can switch to the synchronous work loop, too.
1009 - // Something to consider for a future refactor.
1010 - const rootWorkInProgress = prepareFreshStack(root, errorRetryLanes);
1011 - rootWorkInProgress.flags |= ForceClientRender;
1019 + if (root.isDehydrated) {
1020 + root.isDehydrated = false;
1021 if (__DEV__) {
1022 errorHydratingContainer(root.containerInfo);
1023 }
1024 + const error = new Error(
1025 + 'There was an error while hydrating. Because the error happened outside ' +
1026 + 'of a Suspense boundary, the entire root will switch to ' +
1027 + 'client rendering.',
1028 + );
1029 + renderDidError(error);
1030 }
1031
1032 + const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
1033 const exitStatus = renderRootSync(root, errorRetryLanes);
1034 if (exitStatus !== RootErrored) {
1035 // Successfully finished rendering on retry
1020 -
1021 - // The errors from the failed first attempt have been recovered. Add
1022 - // them to the collection of recoverable errors. We'll log them in the
1023 - // commit phase.
1024 - const errorsFromSecondAttempt = workInProgressRootRecoverableErrors;
1025 - workInProgressRootRecoverableErrors = errorsFromFirstAttempt;
1026 - // The errors from the second attempt should be queued after the errors
1027 - // from the first attempt, to preserve the causal sequence.
1028 - if (errorsFromSecondAttempt !== null) {
1029 - queueRecoverableErrors(errorsFromSecondAttempt);
1036 + if (errorsFromFirstAttempt !== null) {
1037 + // The errors from the failed first attempt have been recovered. Add
1038 + // them to the collection of recoverable errors. We'll log them in the
1039 + // commit phase.
1040 + queueRecoverableErrors(errorsFromFirstAttempt);
1041 }
1042 } else {
1043 // The UI failed to recover.
@@ -1442,7 +1453,7 @@ export function popRenderLanes(fiber: Fiber) {
1453 popFromStack(subtreeRenderLanesCursor, fiber);
1454 }
1455
1445 -function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1456 +function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
1457 root.finishedWork = null;
1458 root.finishedLanes = NoLanes;
1459
@@ -1468,8 +1479,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1479 }
1480 }
1481 workInProgressRoot = root;
1471 - const rootWorkInProgress = createWorkInProgress(root.current, null);
1472 - workInProgress = rootWorkInProgress;
1482 + workInProgress = createWorkInProgress(root.current, null);
1483 workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
1484 workInProgressRootExitStatus = RootInProgress;
1485 workInProgressRootFatalError = null;
@@ -1485,8 +1495,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1495 if (__DEV__) {
1496 ReactStrictModeWarnings.discardPendingWarnings();
1497 }
1488 -
1489 - return rootWorkInProgress;
1498 }
1499
1500 function handleError(root, thrownValue): void {
packages/react-reconciler/src/ReactInternalTypes.js
+2
@@ -213,6 +213,8 @@ type BaseFiberRootProperties = {|
213 // Top context object, used by renderSubtreeIntoContainer
214 context: Object | null,
215 pendingContext: Object | null,
216 + // Determines if we should attempt to hydrate on the initial mount
217 + +isDehydrated: boolean,
218
219 // Used by useMutableSource hook to avoid tearing during hydration.
220 mutableSourceEagerHydrationData?: Array<
packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
+2
@@ -72,6 +72,7 @@ describe('ReactFiberHostContext', () => {
72 const container = Renderer.createContainer(
73 /* root: */ null,
74 ConcurrentRoot,
75 + false,
76 null,
77 false,
78 '',
@@ -135,6 +136,7 @@ describe('ReactFiberHostContext', () => {
136 const container = Renderer.createContainer(
137 rootContext,
138 ConcurrentRoot,
139 + false,
140 null,
141 false,
142 '',
packages/react-test-renderer/src/ReactTestRenderer.js
+1
@@ -473,6 +473,7 @@ function create(element: React$Element<any>, options: TestRendererOptions) {
473 let root: FiberRoot | null = createContainer(
474 container,
475 isConcurrent ? ConcurrentRoot : LegacyRoot,
476 + false,
477 null,
478 isStrictMode,
479 concurrentUpdatesByDefault,