Revert accdientally merged PR (#24081)

This commit is contained in:
Andrew Clark authored and GitHub committed 2022-03-11 21:31:23 -05:00
1 parent 02b65fd8c5
commit 832e2987e0
26 files changed
+291 -565

No files matched your search

+1
View File
@@ -69,6 +69,7 @@ class Surface extends React.Component {
this._mountNode = createContainer(
this._surface,
LegacyRoot,
false,
null,
false,
false,
@@ -9,7 +9,6 @@
let JSDOM;
let React;
let startTransition;
let ReactDOMClient;
let Scheduler;
let clientAct;
@@ -34,8 +33,6 @@ describe('ReactDOMFizzShellHydration', () => {
ReactDOMFizzServer = require('react-dom/server');
Stream = require('stream');
startTransition = React.startTransition;
textCache = new Map();
// Test Environment
@@ -217,36 +214,7 @@ describe('ReactDOMFizzShellHydration', () => {
expect(container.textContent).toBe('Shell');
});
test(
'updating the root at lower priority than initial hydration does not ' +
'force a client render',
async () => {
function App() {
return <Text text="Initial" />;
}
// Server render
await resolveText('Initial');
await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
pipe(writable);
});
expect(Scheduler).toHaveYielded(['Initial']);
await clientAct(async () => {
const root = ReactDOMClient.hydrateRoot(container, <App />);
// This has lower priority than the initial hydration, so the update
// won't be processed until after hydration finishes.
startTransition(() => {
root.render(<Text text="Updated" />);
});
});
expect(Scheduler).toHaveYielded(['Initial', 'Updated']);
expect(container.textContent).toBe('Updated');
},
);
test('updating the root while the shell is suspended forces a client render', async () => {
test('updating the root before the shell hydrates forces a client render', async () => {
function App() {
return <AsyncText text="Shell" />;
}
@@ -277,9 +245,9 @@ describe('ReactDOMFizzShellHydration', () => {
root.render(<Text text="New screen" />);
});
expect(Scheduler).toHaveYielded([
'New screen',
'This root received an early update, before anything was able ' +
'hydrate. Switched the entire root to client rendering.',
'New screen',
]);
expect(container.textContent).toBe('New screen');
});
-9
View File
@@ -253,15 +253,6 @@ describe('ReactDOMRoot', () => {
);
});
it('callback passed to legacy hydrate() API', () => {
container.innerHTML = '<div>Hi</div>';
ReactDOM.hydrate(<div>Hi</div>, container, () => {
Scheduler.unstable_yieldValue('callback');
});
expect(container.textContent).toEqual('Hi');
expect(Scheduler).toHaveYielded(['callback']);
});
it('warns when unmounting with legacy API (no previous content)', () => {
const root = ReactDOMClient.createRoot(container);
root.render(<div>Hi</div>);
+43 -82
View File
@@ -27,7 +27,6 @@ import {
import {
createContainer,
createHydrationContainer,
findHostInstanceWithNoPortals,
updateContainer,
flushSync,
@@ -110,81 +109,34 @@ function noopOnRecoverableError() {
function legacyCreateRootFromDOMContainer(
container: Container,
initialChildren: ReactNodeList,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
isHydrationContainer: boolean,
forceHydrate: boolean,
): FiberRoot {
if (isHydrationContainer) {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root);
originalCallback.call(instance);
};
}
const root = createHydrationContainer(
initialChildren,
callback,
container,
LegacyRoot,
null, // hydrationCallbacks
false, // isStrictMode
false, // concurrentUpdatesByDefaultOverride,
'', // identifierPrefix
noopOnRecoverableError,
// TODO(luna) Support hydration later
null,
);
container._reactRootContainer = root;
markContainerAsRoot(root.current, container);
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
listenToAllSupportedEvents(rootContainerElement);
flushSync();
return root;
} else {
// First clear any existing content.
// First clear any existing content.
if (!forceHydrate) {
let rootSibling;
while ((rootSibling = container.lastChild)) {
container.removeChild(rootSibling);
}
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root);
originalCallback.call(instance);
};
}
const root = createContainer(
container,
LegacyRoot,
null, // hydrationCallbacks
false, // isStrictMode
false, // concurrentUpdatesByDefaultOverride,
'', // identifierPrefix
noopOnRecoverableError, // onRecoverableError
null, // transitionCallbacks
);
container._reactRootContainer = root;
markContainerAsRoot(root.current, container);
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
listenToAllSupportedEvents(rootContainerElement);
// Initial mount should not be batched.
flushSync(() => {
updateContainer(initialChildren, root, parentComponent, callback);
});
return root;
}
const root = createContainer(
container,
LegacyRoot,
forceHydrate,
null, // hydrationCallbacks
false, // isStrictMode
false, // concurrentUpdatesByDefaultOverride,
'', // identifierPrefix
noopOnRecoverableError, // onRecoverableError
null, // transitionCallbacks
);
markContainerAsRoot(root.current, container);
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
listenToAllSupportedEvents(rootContainerElement);
return root;
}
function warnOnInvalidCallback(callback: mixed, callerName: string): void {
@@ -212,30 +164,39 @@ function legacyRenderSubtreeIntoContainer(
warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
}
const maybeRoot = container._reactRootContainer;
let root: FiberRoot;
if (!maybeRoot) {
let root = container._reactRootContainer;
let fiberRoot: FiberRoot;
if (!root) {
// Initial mount
root = legacyCreateRootFromDOMContainer(
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
children,
parentComponent,
callback,
forceHydrate,
);
} else {
root = maybeRoot;
fiberRoot = root;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root);
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
flushSync(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
updateContainer(children, root, parentComponent, callback);
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(root);
return getPublicRootInstance(fiberRoot);
}
export function findDOMNode(
+1 -1
View File
@@ -225,6 +225,7 @@ export function createRoot(
const root = createContainer(
container,
ConcurrentRoot,
false,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
@@ -301,7 +302,6 @@ export function hydrateRoot(
const root = createHydrationContainer(
initialChildren,
null,
container,
ConcurrentRoot,
hydrationCallbacks,
+1 -2
View File
@@ -53,7 +53,6 @@ import {
setCurrentUpdatePriority,
} from 'react-reconciler/src/ReactEventPriorities';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
const {ReactCurrentBatchConfig} = ReactSharedInternals;
@@ -387,7 +386,7 @@ export function findInstanceBlockingEvent(
targetInst = null;
} else if (tag === HostRoot) {
const root: FiberRoot = nearestMounted.stateNode;
if (isRootDehydrated(root)) {
if (root.isDehydrated) {
// If this happens during a replay something went wrong and it might block
// the whole system.
return getContainerFromFiber(nearestMounted);
+1 -2
View File
@@ -39,7 +39,6 @@ import {
} from '../client/ReactDOMComponentTree';
import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities';
import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
let _attemptSynchronousHydration: (fiber: Object) => void;
@@ -415,7 +414,7 @@ function attemptExplicitHydrationTarget(
}
} else if (tag === HostRoot) {
const root: FiberRoot = nearestMounted.stateNode;
if (isRootDehydrated(root)) {
if (root.isDehydrated) {
queuedTarget.blockedOn = getContainerFromFiber(nearestMounted);
// We don't currently have a way to increase the priority of
// a root other than sync.
+1
View File
@@ -215,6 +215,7 @@ function render(
root = createContainer(
containerTag,
concurrentRoot ? ConcurrentRoot : LegacyRoot,
false,
null,
false,
null,
@@ -211,6 +211,7 @@ function render(
root = createContainer(
containerTag,
LegacyRoot,
false,
null,
false,
null,
+3
View File
@@ -974,6 +974,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
root = NoopRenderer.createContainer(
container,
tag,
false,
null,
null,
false,
@@ -995,6 +996,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
const fiberRoot = NoopRenderer.createContainer(
container,
ConcurrentRoot,
false,
null,
null,
false,
@@ -1027,6 +1029,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
const fiberRoot = NoopRenderer.createContainer(
container,
LegacyRoot,
false,
null,
null,
false,
@@ -7,11 +7,7 @@
* @flow
*/
import type {
ReactProviderType,
ReactContext,
ReactNodeList,
} from 'shared/ReactTypes';
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {TypeOfMode} from './ReactTypeOfMode';
@@ -33,7 +29,6 @@ import type {
SpawnedCachePool,
} from './ReactFiberCacheComponent.new';
import type {UpdateQueue} from './ReactUpdateQueue.new';
import type {RootState} from './ReactFiberRoot.new';
import {
enableSuspenseAvoidThisFallback,
enableCPUSuspense,
@@ -1316,7 +1311,7 @@ function pushHostRootContext(workInProgress) {
function updateHostRoot(current, workInProgress, renderLanes) {
pushHostRootContext(workInProgress);
const updateQueue: UpdateQueue<RootState> = (workInProgress.updateQueue: any);
const updateQueue = workInProgress.updateQueue;
if (current === null || updateQueue === null) {
throw new Error(
@@ -1331,7 +1326,7 @@ function updateHostRoot(current, workInProgress, renderLanes) {
const prevChildren = prevState.element;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState: RootState = workInProgress.memoizedState;
const nextState = workInProgress.memoizedState;
const root: FiberRoot = workInProgress.stateNode;
@@ -1346,130 +1341,64 @@ function updateHostRoot(current, workInProgress, renderLanes) {
}
if (enableTransitionTracing) {
// FIXME: Slipped past code review. This is not a safe mutation:
// workInProgress.memoizedState is a shared object. Need to fix before
// rolling out the Transition Tracing experiment.
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
}
// Caution: React DevTools currently depends on this property
// being called "element".
const nextChildren = nextState.element;
if (supportsHydration && prevState.isDehydrated) {
// This is a hydration root whose shell has not yet hydrated. We should
// attempt to hydrate.
if (workInProgress.flags & ForceClientRender) {
// Something errored during a previous attempt to hydrate the shell, so we
// forced a client render.
const recoverableError = new Error(
'There was an error while hydrating. Because the error happened outside ' +
'of a Suspense boundary, the entire root will switch to ' +
'client rendering.',
);
return mountHostRootWithoutHydrating(
current,
workInProgress,
updateQueue,
nextState,
nextChildren,
renderLanes,
recoverableError,
);
} else if (nextChildren !== prevChildren) {
const recoverableError = new Error(
'This root received an early update, before anything was able ' +
'hydrate. Switched the entire root to client rendering.',
);
return mountHostRootWithoutHydrating(
current,
workInProgress,
updateQueue,
nextState,
nextChildren,
renderLanes,
recoverableError,
);
} else {
// The outermost shell has not hydrated yet. Start hydrating.
enterHydrationState(workInProgress);
if (supportsHydration) {
const mutableSourceEagerHydrationData =
root.mutableSourceEagerHydrationData;
if (mutableSourceEagerHydrationData != null) {
for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
const mutableSource = ((mutableSourceEagerHydrationData[
i
]: any): MutableSource<any>);
const version = mutableSourceEagerHydrationData[i + 1];
setWorkInProgressVersion(mutableSource, version);
}
if (nextChildren === prevChildren) {
resetHydrationState();
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
if (root.isDehydrated && enterHydrationState(workInProgress)) {
// If we don't have any current children this might be the first pass.
// We always try to hydrate. If this isn't a hydration pass there won't
// be any children to hydrate which is effectively the same thing as
// not hydrating.
if (supportsHydration) {
const mutableSourceEagerHydrationData =
root.mutableSourceEagerHydrationData;
if (mutableSourceEagerHydrationData != null) {
for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
const mutableSource = ((mutableSourceEagerHydrationData[
i
]: any): MutableSource<any>);
const version = mutableSourceEagerHydrationData[i + 1];
setWorkInProgressVersion(mutableSource, version);
}
}
}
const child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
workInProgress.child = child;
const child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
workInProgress.child = child;
let node = child;
while (node) {
// Mark each child as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
node.flags = (node.flags & ~Placement) | Hydrating;
node = node.sibling;
}
let node = child;
while (node) {
// Mark each child as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
node.flags = (node.flags & ~Placement) | Hydrating;
node = node.sibling;
}
} else {
// Root is not dehydrated. Either this is a client-only root, or it
// already hydrated.
resetHydrationState();
if (nextChildren === prevChildren) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// Otherwise reset hydration state in case we aborted and resumed another
// root.
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
resetHydrationState();
}
return workInProgress.child;
}
function mountHostRootWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
updateQueue: UpdateQueue<RootState>,
nextState: RootState,
nextChildren: ReactNodeList,
renderLanes: Lanes,
recoverableError: Error,
) {
// Revert to client rendering.
resetHydrationState();
queueHydrationError(recoverableError);
workInProgress.flags |= ForceClientRender;
// Flip isDehydrated to false to indicate that when this render
// finishes, the root will no longer be dehydrated.
const overrideState: RootState = {
element: nextChildren,
isDehydrated: false,
cache: nextState.cache,
transitions: nextState.transitions,
};
// `baseState` can always be the last state because the root doesn't
// have reducer functions so it doesn't need rebasing.
updateQueue.baseState = overrideState;
workInProgress.memoizedState = overrideState;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
@@ -7,11 +7,7 @@
* @flow
*/
import type {
ReactProviderType,
ReactContext,
ReactNodeList,
} from 'shared/ReactTypes';
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {TypeOfMode} from './ReactTypeOfMode';
@@ -33,7 +29,6 @@ import type {
SpawnedCachePool,
} from './ReactFiberCacheComponent.old';
import type {UpdateQueue} from './ReactUpdateQueue.old';
import type {RootState} from './ReactFiberRoot.old';
import {
enableSuspenseAvoidThisFallback,
enableCPUSuspense,
@@ -1316,7 +1311,7 @@ function pushHostRootContext(workInProgress) {
function updateHostRoot(current, workInProgress, renderLanes) {
pushHostRootContext(workInProgress);
const updateQueue: UpdateQueue<RootState> = (workInProgress.updateQueue: any);
const updateQueue = workInProgress.updateQueue;
if (current === null || updateQueue === null) {
throw new Error(
@@ -1331,7 +1326,7 @@ function updateHostRoot(current, workInProgress, renderLanes) {
const prevChildren = prevState.element;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState: RootState = workInProgress.memoizedState;
const nextState = workInProgress.memoizedState;
const root: FiberRoot = workInProgress.stateNode;
@@ -1346,130 +1341,64 @@ function updateHostRoot(current, workInProgress, renderLanes) {
}
if (enableTransitionTracing) {
// FIXME: Slipped past code review. This is not a safe mutation:
// workInProgress.memoizedState is a shared object. Need to fix before
// rolling out the Transition Tracing experiment.
workInProgress.memoizedState.transitions = getWorkInProgressTransitions();
}
// Caution: React DevTools currently depends on this property
// being called "element".
const nextChildren = nextState.element;
if (supportsHydration && prevState.isDehydrated) {
// This is a hydration root whose shell has not yet hydrated. We should
// attempt to hydrate.
if (workInProgress.flags & ForceClientRender) {
// Something errored during a previous attempt to hydrate the shell, so we
// forced a client render.
const recoverableError = new Error(
'There was an error while hydrating. Because the error happened outside ' +
'of a Suspense boundary, the entire root will switch to ' +
'client rendering.',
);
return mountHostRootWithoutHydrating(
current,
workInProgress,
updateQueue,
nextState,
nextChildren,
renderLanes,
recoverableError,
);
} else if (nextChildren !== prevChildren) {
const recoverableError = new Error(
'This root received an early update, before anything was able ' +
'hydrate. Switched the entire root to client rendering.',
);
return mountHostRootWithoutHydrating(
current,
workInProgress,
updateQueue,
nextState,
nextChildren,
renderLanes,
recoverableError,
);
} else {
// The outermost shell has not hydrated yet. Start hydrating.
enterHydrationState(workInProgress);
if (supportsHydration) {
const mutableSourceEagerHydrationData =
root.mutableSourceEagerHydrationData;
if (mutableSourceEagerHydrationData != null) {
for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
const mutableSource = ((mutableSourceEagerHydrationData[
i
]: any): MutableSource<any>);
const version = mutableSourceEagerHydrationData[i + 1];
setWorkInProgressVersion(mutableSource, version);
}
if (nextChildren === prevChildren) {
resetHydrationState();
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
if (root.isDehydrated && enterHydrationState(workInProgress)) {
// If we don't have any current children this might be the first pass.
// We always try to hydrate. If this isn't a hydration pass there won't
// be any children to hydrate which is effectively the same thing as
// not hydrating.
if (supportsHydration) {
const mutableSourceEagerHydrationData =
root.mutableSourceEagerHydrationData;
if (mutableSourceEagerHydrationData != null) {
for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
const mutableSource = ((mutableSourceEagerHydrationData[
i
]: any): MutableSource<any>);
const version = mutableSourceEagerHydrationData[i + 1];
setWorkInProgressVersion(mutableSource, version);
}
}
}
const child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
workInProgress.child = child;
const child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
workInProgress.child = child;
let node = child;
while (node) {
// Mark each child as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
node.flags = (node.flags & ~Placement) | Hydrating;
node = node.sibling;
}
let node = child;
while (node) {
// Mark each child as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
node.flags = (node.flags & ~Placement) | Hydrating;
node = node.sibling;
}
} else {
// Root is not dehydrated. Either this is a client-only root, or it
// already hydrated.
resetHydrationState();
if (nextChildren === prevChildren) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// Otherwise reset hydration state in case we aborted and resumed another
// root.
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
resetHydrationState();
}
return workInProgress.child;
}
function mountHostRootWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
updateQueue: UpdateQueue<RootState>,
nextState: RootState,
nextChildren: ReactNodeList,
renderLanes: Lanes,
recoverableError: Error,
) {
// Revert to client rendering.
resetHydrationState();
queueHydrationError(recoverableError);
workInProgress.flags |= ForceClientRender;
// Flip isDehydrated to false to indicate that when this render
// finishes, the root will no longer be dehydrated.
const overrideState: RootState = {
element: nextChildren,
isDehydrated: false,
cache: nextState.cache,
transitions: nextState.transitions,
};
// `baseState` can always be the last state because the root doesn't
// have reducer functions so it doesn't need rebasing.
updateQueue.baseState = overrideState;
workInProgress.memoizedState = overrideState;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
@@ -25,7 +25,6 @@ import type {Wakeable} from 'shared/ReactTypes';
import type {OffscreenState} from './ReactFiberOffscreenComponent';
import type {HookFlags} from './ReactHookEffectTags';
import type {Cache} from './ReactFiberCacheComponent.new';
import type {RootState} from './ReactFiberRoot.new';
import {
enableCreateEventHandleAPI,
@@ -1878,12 +1877,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
}
case HostRoot: {
if (supportsHydration) {
if (current !== null) {
const prevRootState: RootState = current.memoizedState;
if (prevRootState.isDehydrated) {
const root: FiberRoot = finishedWork.stateNode;
commitHydratedContainer(root.containerInfo);
}
const root: FiberRoot = finishedWork.stateNode;
if (root.isDehydrated) {
// We've just hydrated. No need to hydrate again.
root.isDehydrated = false;
commitHydratedContainer(root.containerInfo);
}
}
break;
@@ -1987,12 +1985,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
}
case HostRoot: {
if (supportsHydration) {
if (current !== null) {
const prevRootState: RootState = current.memoizedState;
if (prevRootState.isDehydrated) {
const root: FiberRoot = finishedWork.stateNode;
commitHydratedContainer(root.containerInfo);
}
const root: FiberRoot = finishedWork.stateNode;
if (root.isDehydrated) {
// We've just hydrated. No need to hydrate again.
root.isDehydrated = false;
commitHydratedContainer(root.containerInfo);
}
}
return;
@@ -25,7 +25,6 @@ import type {Wakeable} from 'shared/ReactTypes';
import type {OffscreenState} from './ReactFiberOffscreenComponent';
import type {HookFlags} from './ReactHookEffectTags';
import type {Cache} from './ReactFiberCacheComponent.old';
import type {RootState} from './ReactFiberRoot.old';
import {
enableCreateEventHandleAPI,
@@ -1878,12 +1877,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
}
case HostRoot: {
if (supportsHydration) {
if (current !== null) {
const prevRootState: RootState = current.memoizedState;
if (prevRootState.isDehydrated) {
const root: FiberRoot = finishedWork.stateNode;
commitHydratedContainer(root.containerInfo);
}
const root: FiberRoot = finishedWork.stateNode;
if (root.isDehydrated) {
// We've just hydrated. No need to hydrate again.
root.isDehydrated = false;
commitHydratedContainer(root.containerInfo);
}
}
break;
@@ -1987,12 +1985,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
}
case HostRoot: {
if (supportsHydration) {
if (current !== null) {
const prevRootState: RootState = current.memoizedState;
if (prevRootState.isDehydrated) {
const root: FiberRoot = finishedWork.stateNode;
commitHydratedContainer(root.containerInfo);
}
const root: FiberRoot = finishedWork.stateNode;
if (root.isDehydrated) {
// We've just hydrated. No need to hydrate again.
root.isDehydrated = false;
commitHydratedContainer(root.containerInfo);
}
}
return;
@@ -8,7 +8,6 @@
*/
import type {Fiber} from './ReactInternalTypes';
import type {RootState} from './ReactFiberRoot.new';
import type {Lanes, Lane} from './ReactFiberLane.new';
import type {
ReactScopeInstance,
@@ -891,29 +890,12 @@ function completeWork(
// If we hydrated, then we'll need to schedule an update for
// the commit side-effects on the root.
markUpdate(workInProgress);
} else {
if (current !== null) {
const prevState: RootState = current.memoizedState;
if (
// Check if this is a client root
!prevState.isDehydrated ||
// Check if we reverted to client rendering (e.g. due to an error)
(workInProgress.flags & ForceClientRender) !== NoFlags
) {
// Schedule an effect to clear this container at the start of the
// next commit. This handles the case of React rendering into a
// container with previous children. It's also safe to do for
// updates too, because current.child would only be null if the
// previous render was null (so the container would already
// be empty).
workInProgress.flags |= Snapshot;
// If this was a forced client render, there may have been
// recoverable errors during first hydration attempt. If so, add
// them to a queue so we can log them in the commit phase.
upgradeHydrationErrorsToRecoverable();
}
}
} else if (!fiberRoot.isDehydrated) {
// Schedule an effect to clear this container at the start of the next commit.
// This handles the case of React rendering into a container with previous children.
// It's also safe to do for updates too, because current.child would only be null
// if the previous render was null (so the container would already be empty).
workInProgress.flags |= Snapshot;
}
}
updateHostContainer(current, workInProgress);
@@ -8,7 +8,6 @@
*/
import type {Fiber} from './ReactInternalTypes';
import type {RootState} from './ReactFiberRoot.old';
import type {Lanes, Lane} from './ReactFiberLane.old';
import type {
ReactScopeInstance,
@@ -891,29 +890,12 @@ function completeWork(
// If we hydrated, then we'll need to schedule an update for
// the commit side-effects on the root.
markUpdate(workInProgress);
} else {
if (current !== null) {
const prevState: RootState = current.memoizedState;
if (
// Check if this is a client root
!prevState.isDehydrated ||
// Check if we reverted to client rendering (e.g. due to an error)
(workInProgress.flags & ForceClientRender) !== NoFlags
) {
// Schedule an effect to clear this container at the start of the
// next commit. This handles the case of React rendering into a
// container with previous children. It's also safe to do for
// updates too, because current.child would only be null if the
// previous render was null (so the container would already
// be empty).
workInProgress.flags |= Snapshot;
// If this was a forced client render, there may have been
// recoverable errors during first hydration attempt. If so, add
// them to a queue so we can log them in the commit phase.
upgradeHydrationErrorsToRecoverable();
}
}
} else if (!fiberRoot.isDehydrated) {
// Schedule an effect to clear this container at the start of the next commit.
// This handles the case of React rendering into a container with previous children.
// It's also safe to do for updates too, because current.child would only be null
// if the previous render was null (so the container would already be empty).
workInProgress.flags |= Snapshot;
}
}
updateHostContainer(current, workInProgress);
@@ -48,7 +48,6 @@ import {
isContextProvider as isLegacyContextProvider,
} from './ReactFiberContext.new';
import {createFiberRoot} from './ReactFiberRoot.new';
import {isRootDehydrated} from './ReactFiberShellHydration';
import {
injectInternals,
markRenderScheduled,
@@ -246,6 +245,9 @@ function findHostInstanceWithWarning(
export function createContainer(
containerInfo: Container,
tag: RootTag,
// TODO: We can remove hydration-specific stuff from createContainer once
// we delete legacy mode. The new root API uses createHydrationContainer.
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
@@ -253,13 +255,10 @@ export function createContainer(
onRecoverableError: (error: mixed) => void,
transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
const hydrate = false;
const initialChildren = null;
return createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
@@ -271,8 +270,6 @@ export function createContainer(
export function createHydrationContainer(
initialChildren: ReactNodeList,
// TODO: Remove `callback` when we delete legacy mode.
callback: ?Function,
containerInfo: Container,
tag: RootTag,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
@@ -287,7 +284,6 @@ export function createHydrationContainer(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
@@ -306,9 +302,9 @@ export function createHydrationContainer(
const eventTime = requestEventTime();
const lane = requestUpdateLane(current);
const update = createUpdate(eventTime, lane);
update.payload = {isDehydrated: false};
update.callback =
callback !== undefined && callback !== null ? callback : null;
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element: initialChildren};
enqueueUpdate(current, update, lane);
scheduleInitialHydrationOnRoot(root, lane, eventTime);
@@ -413,7 +409,7 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
switch (fiber.tag) {
case HostRoot:
const root: FiberRoot = fiber.stateNode;
if (isRootDehydrated(root)) {
if (root.isDehydrated) {
// Flush the first scheduled "update".
const lanes = getHighestPriorityPendingLanes(root);
flushRoot(root, lanes);
@@ -48,7 +48,6 @@ import {
isContextProvider as isLegacyContextProvider,
} from './ReactFiberContext.old';
import {createFiberRoot} from './ReactFiberRoot.old';
import {isRootDehydrated} from './ReactFiberShellHydration';
import {
injectInternals,
markRenderScheduled,
@@ -246,6 +245,9 @@ function findHostInstanceWithWarning(
export function createContainer(
containerInfo: Container,
tag: RootTag,
// TODO: We can remove hydration-specific stuff from createContainer once
// we delete legacy mode. The new root API uses createHydrationContainer.
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
@@ -253,13 +255,10 @@ export function createContainer(
onRecoverableError: (error: mixed) => void,
transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
const hydrate = false;
const initialChildren = null;
return createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
@@ -271,8 +270,6 @@ export function createContainer(
export function createHydrationContainer(
initialChildren: ReactNodeList,
// TODO: Remove `callback` when we delete legacy mode.
callback: ?Function,
containerInfo: Container,
tag: RootTag,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
@@ -287,7 +284,6 @@ export function createHydrationContainer(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
@@ -306,9 +302,9 @@ export function createHydrationContainer(
const eventTime = requestEventTime();
const lane = requestUpdateLane(current);
const update = createUpdate(eventTime, lane);
update.payload = {isDehydrated: false};
update.callback =
callback !== undefined && callback !== null ? callback : null;
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element: initialChildren};
enqueueUpdate(current, update, lane);
scheduleInitialHydrationOnRoot(root, lane, eventTime);
@@ -413,7 +409,7 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
switch (fiber.tag) {
case HostRoot:
const root: FiberRoot = fiber.stateNode;
if (isRootDehydrated(root)) {
if (root.isDehydrated) {
// Flush the first scheduled "update".
const lanes = getHighestPriorityPendingLanes(root);
flushRoot(root, lanes);
@@ -7,7 +7,6 @@
* @flow
*/
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
FiberRoot,
SuspenseHydrationCallbacks,
@@ -40,8 +39,7 @@ import {createCache, retainCache} from './ReactFiberCacheComponent.new';
export type RootState = {
element: any,
isDehydrated: boolean,
cache: Cache,
cache: Cache | null,
transitions: Transitions | null,
};
@@ -61,6 +59,7 @@ function FiberRootNode(
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.isDehydrated = hydrate;
this.callbackNode = null;
this.callbackPriority = NoLane;
this.eventTimes = createLaneMap(NoLanes);
@@ -129,7 +128,6 @@ export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
initialChildren: ReactNodeList,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
@@ -180,17 +178,15 @@ export function createFiberRoot(
root.pooledCache = initialCache;
retainCache(initialCache);
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
element: null,
cache: initialCache,
transitions: null,
};
uninitializedFiber.memoizedState = initialState;
} else {
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: (null: any), // not enabled yet
element: null,
cache: null,
transitions: null,
};
uninitializedFiber.memoizedState = initialState;
@@ -7,7 +7,6 @@
* @flow
*/
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
FiberRoot,
SuspenseHydrationCallbacks,
@@ -40,8 +39,7 @@ import {createCache, retainCache} from './ReactFiberCacheComponent.old';
export type RootState = {
element: any,
isDehydrated: boolean,
cache: Cache,
cache: Cache | null,
transitions: Transitions | null,
};
@@ -61,6 +59,7 @@ function FiberRootNode(
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.isDehydrated = hydrate;
this.callbackNode = null;
this.callbackPriority = NoLane;
this.eventTimes = createLaneMap(NoLanes);
@@ -129,7 +128,6 @@ export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
initialChildren: ReactNodeList,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
@@ -180,17 +178,15 @@ export function createFiberRoot(
root.pooledCache = initialCache;
retainCache(initialCache);
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
element: null,
cache: initialCache,
transitions: null,
};
uninitializedFiber.memoizedState = initialState;
} else {
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: (null: any), // not enabled yet
element: null,
cache: null,
transitions: null,
};
uninitializedFiber.memoizedState = initialState;
@@ -1,19 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {FiberRoot} from './ReactInternalTypes';
import type {RootState} from './ReactFiberRoot.new';
// This is imported by the event replaying implementation in React DOM. It's
// in a separate file to break a circular dependency between the renderer and
// the reconciler.
export function isRootDehydrated(root: FiberRoot) {
const currentState: RootState = root.current.memoizedState;
return currentState.isDehydrated;
}
@@ -88,7 +88,6 @@ import {
createWorkInProgress,
assignFiberPropertiesInDEV,
} from './ReactFiber.new';
import {isRootDehydrated} from './ReactFiberShellHydration';
import {NoMode, ProfileMode, ConcurrentMode} from './ReactTypeOfMode';
import {
HostRoot,
@@ -110,7 +109,6 @@ import {
StoreConsistency,
HostEffectMask,
Hydrating,
ForceClientRender,
BeforeMutationMask,
MutationMask,
LayoutMask,
@@ -583,7 +581,34 @@ export function scheduleUpdateOnFiber(
}
}
if (root === workInProgressRoot) {
if (root.isDehydrated && root.tag !== LegacyRoot) {
// This root's shell hasn't hydrated yet. Revert to client rendering.
if (workInProgressRoot === root) {
// If this happened during an interleaved event, interrupt the
// in-progress hydration. Theoretically, we could attempt to force a
// synchronous hydration before switching to client rendering, but the
// most common reason the shell hasn't hydrated yet is because it
// suspended. So it's very likely to suspend again anyway. For
// simplicity, we'll skip that atttempt and go straight to
// client rendering.
//
// Another way to model this would be to give the initial hydration its
// own special lane. However, it may not be worth adding a lane solely
// for this purpose, so we'll wait until we find another use case before
// adding it.
//
// TODO: Consider only interrupting hydration if the priority of the
// update is higher than default.
prepareFreshStack(root, NoLanes);
}
root.isDehydrated = false;
const error = new Error(
'This root received an early update, before anything was able ' +
'hydrate. Switched the entire root to client rendering.',
);
const onRecoverableError = root.onRecoverableError;
onRecoverableError(error);
} else if (root === workInProgressRoot) {
// TODO: Consolidate with `isInterleavedUpdate` check
// Received an update to a tree that's in the middle of rendering. Mark
@@ -991,42 +1016,28 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
function recoverFromConcurrentError(root, errorRetryLanes) {
// If an error occurred during hydration, discard server response and fall
// back to client side render.
// Before rendering again, save the errors from the previous attempt.
const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
if (isRootDehydrated(root)) {
// The shell failed to hydrate. Set a flag to force a client rendering
// during the next attempt. To do this, we call prepareFreshStack now
// to create the root work-in-progress fiber. This is a bit weird in terms
// of factoring, because it relies on renderRootSync not calling
// prepareFreshStack again in the call below, which happens because the
// root and lanes haven't changed.
//
// TODO: I think what we should do is set ForceClientRender inside
// throwException, like we do for nested Suspense boundaries. The reason
// it's here instead is so we can switch to the synchronous work loop, too.
// Something to consider for a future refactor.
const rootWorkInProgress = prepareFreshStack(root, errorRetryLanes);
rootWorkInProgress.flags |= ForceClientRender;
if (root.isDehydrated) {
root.isDehydrated = false;
if (__DEV__) {
errorHydratingContainer(root.containerInfo);
}
const error = new Error(
'There was an error while hydrating. Because the error happened outside ' +
'of a Suspense boundary, the entire root will switch to ' +
'client rendering.',
);
renderDidError(error);
}
const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
const exitStatus = renderRootSync(root, errorRetryLanes);
if (exitStatus !== RootErrored) {
// Successfully finished rendering on retry
// The errors from the failed first attempt have been recovered. Add
// them to the collection of recoverable errors. We'll log them in the
// commit phase.
const errorsFromSecondAttempt = workInProgressRootRecoverableErrors;
workInProgressRootRecoverableErrors = errorsFromFirstAttempt;
// The errors from the second attempt should be queued after the errors
// from the first attempt, to preserve the causal sequence.
if (errorsFromSecondAttempt !== null) {
queueRecoverableErrors(errorsFromSecondAttempt);
if (errorsFromFirstAttempt !== null) {
// The errors from the failed first attempt have been recovered. Add
// them to the collection of recoverable errors. We'll log them in the
// commit phase.
queueRecoverableErrors(errorsFromFirstAttempt);
}
} else {
// The UI failed to recover.
@@ -1442,7 +1453,7 @@ export function popRenderLanes(fiber: Fiber) {
popFromStack(subtreeRenderLanesCursor, fiber);
}
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
root.finishedWork = null;
root.finishedLanes = NoLanes;
@@ -1468,8 +1479,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
}
}
workInProgressRoot = root;
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgress = createWorkInProgress(root.current, null);
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
workInProgressRootExitStatus = RootInProgress;
workInProgressRootFatalError = null;
@@ -1485,8 +1495,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
if (__DEV__) {
ReactStrictModeWarnings.discardPendingWarnings();
}
return rootWorkInProgress;
}
function handleError(root, thrownValue): void {
@@ -88,7 +88,6 @@ import {
createWorkInProgress,
assignFiberPropertiesInDEV,
} from './ReactFiber.old';
import {isRootDehydrated} from './ReactFiberShellHydration';
import {NoMode, ProfileMode, ConcurrentMode} from './ReactTypeOfMode';
import {
HostRoot,
@@ -110,7 +109,6 @@ import {
StoreConsistency,
HostEffectMask,
Hydrating,
ForceClientRender,
BeforeMutationMask,
MutationMask,
LayoutMask,
@@ -583,7 +581,34 @@ export function scheduleUpdateOnFiber(
}
}
if (root === workInProgressRoot) {
if (root.isDehydrated && root.tag !== LegacyRoot) {
// This root's shell hasn't hydrated yet. Revert to client rendering.
if (workInProgressRoot === root) {
// If this happened during an interleaved event, interrupt the
// in-progress hydration. Theoretically, we could attempt to force a
// synchronous hydration before switching to client rendering, but the
// most common reason the shell hasn't hydrated yet is because it
// suspended. So it's very likely to suspend again anyway. For
// simplicity, we'll skip that atttempt and go straight to
// client rendering.
//
// Another way to model this would be to give the initial hydration its
// own special lane. However, it may not be worth adding a lane solely
// for this purpose, so we'll wait until we find another use case before
// adding it.
//
// TODO: Consider only interrupting hydration if the priority of the
// update is higher than default.
prepareFreshStack(root, NoLanes);
}
root.isDehydrated = false;
const error = new Error(
'This root received an early update, before anything was able ' +
'hydrate. Switched the entire root to client rendering.',
);
const onRecoverableError = root.onRecoverableError;
onRecoverableError(error);
} else if (root === workInProgressRoot) {
// TODO: Consolidate with `isInterleavedUpdate` check
// Received an update to a tree that's in the middle of rendering. Mark
@@ -991,42 +1016,28 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
function recoverFromConcurrentError(root, errorRetryLanes) {
// If an error occurred during hydration, discard server response and fall
// back to client side render.
// Before rendering again, save the errors from the previous attempt.
const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
if (isRootDehydrated(root)) {
// The shell failed to hydrate. Set a flag to force a client rendering
// during the next attempt. To do this, we call prepareFreshStack now
// to create the root work-in-progress fiber. This is a bit weird in terms
// of factoring, because it relies on renderRootSync not calling
// prepareFreshStack again in the call below, which happens because the
// root and lanes haven't changed.
//
// TODO: I think what we should do is set ForceClientRender inside
// throwException, like we do for nested Suspense boundaries. The reason
// it's here instead is so we can switch to the synchronous work loop, too.
// Something to consider for a future refactor.
const rootWorkInProgress = prepareFreshStack(root, errorRetryLanes);
rootWorkInProgress.flags |= ForceClientRender;
if (root.isDehydrated) {
root.isDehydrated = false;
if (__DEV__) {
errorHydratingContainer(root.containerInfo);
}
const error = new Error(
'There was an error while hydrating. Because the error happened outside ' +
'of a Suspense boundary, the entire root will switch to ' +
'client rendering.',
);
renderDidError(error);
}
const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
const exitStatus = renderRootSync(root, errorRetryLanes);
if (exitStatus !== RootErrored) {
// Successfully finished rendering on retry
// The errors from the failed first attempt have been recovered. Add
// them to the collection of recoverable errors. We'll log them in the
// commit phase.
const errorsFromSecondAttempt = workInProgressRootRecoverableErrors;
workInProgressRootRecoverableErrors = errorsFromFirstAttempt;
// The errors from the second attempt should be queued after the errors
// from the first attempt, to preserve the causal sequence.
if (errorsFromSecondAttempt !== null) {
queueRecoverableErrors(errorsFromSecondAttempt);
if (errorsFromFirstAttempt !== null) {
// The errors from the failed first attempt have been recovered. Add
// them to the collection of recoverable errors. We'll log them in the
// commit phase.
queueRecoverableErrors(errorsFromFirstAttempt);
}
} else {
// The UI failed to recover.
@@ -1442,7 +1453,7 @@ export function popRenderLanes(fiber: Fiber) {
popFromStack(subtreeRenderLanesCursor, fiber);
}
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
root.finishedWork = null;
root.finishedLanes = NoLanes;
@@ -1468,8 +1479,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
}
}
workInProgressRoot = root;
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgress = createWorkInProgress(root.current, null);
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
workInProgressRootExitStatus = RootInProgress;
workInProgressRootFatalError = null;
@@ -1485,8 +1495,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
if (__DEV__) {
ReactStrictModeWarnings.discardPendingWarnings();
}
return rootWorkInProgress;
}
function handleError(root, thrownValue): void {
+2
View File
@@ -213,6 +213,8 @@ type BaseFiberRootProperties = {|
// Top context object, used by renderSubtreeIntoContainer
context: Object | null,
pendingContext: Object | null,
// Determines if we should attempt to hydrate on the initial mount
+isDehydrated: boolean,
// Used by useMutableSource hook to avoid tearing during hydration.
mutableSourceEagerHydrationData?: Array<
@@ -72,6 +72,7 @@ describe('ReactFiberHostContext', () => {
const container = Renderer.createContainer(
/* root: */ null,
ConcurrentRoot,
false,
null,
false,
'',
@@ -135,6 +136,7 @@ describe('ReactFiberHostContext', () => {
const container = Renderer.createContainer(
rootContext,
ConcurrentRoot,
false,
null,
false,
'',
+1
View File
@@ -473,6 +473,7 @@ function create(element: React$Element<any>, options: TestRendererOptions) {
let root: FiberRoot | null = createContainer(
container,
isConcurrent ? ConcurrentRoot : LegacyRoot,
false,
null,
isStrictMode,
concurrentUpdatesByDefault,