mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Revert accdientally merged PR (#24081)
This commit is contained in:
1 parent
02b65fd8c5
commit
832e2987e0
26 files changed
+291
-565
No files matched your search
Vendored
+1
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
'',
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user