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,