mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Land enableClientRenderFallbackOnTextMismatch flag (#24405)
This flag is already enabled on all relevant surfaces. We can remove it.
This commit is contained in:
@@ -14,8 +14,6 @@ let React;
|
||||
let ReactDOMServer;
|
||||
let PropTypes;
|
||||
let ReactCurrentDispatcher;
|
||||
const enableSuspenseServerRenderer = require('shared/ReactFeatureFlags')
|
||||
.enableSuspenseServerRenderer;
|
||||
|
||||
describe('ReactDOMServer', () => {
|
||||
beforeEach(() => {
|
||||
@@ -678,41 +676,6 @@ describe('ReactDOMServer', () => {
|
||||
expect(markup).toBe('<div></div>');
|
||||
});
|
||||
|
||||
if (!enableSuspenseServerRenderer) {
|
||||
it('throws for unsupported types on the server', () => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<React.Suspense />);
|
||||
}).toThrow('ReactDOMServer does not yet support Suspense.');
|
||||
|
||||
async function fakeImport(result) {
|
||||
return {default: result};
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
const LazyFoo = React.lazy(() =>
|
||||
fakeImport(
|
||||
new Promise(resolve =>
|
||||
resolve(function Foo() {
|
||||
return <div />;
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
ReactDOMServer.renderToString(<LazyFoo />);
|
||||
}).toThrow('ReactDOMServer does not yet support Suspense.');
|
||||
});
|
||||
|
||||
it('throws when suspending on the server', () => {
|
||||
function AsyncFoo() {
|
||||
throw new Promise(() => {});
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<AsyncFoo />);
|
||||
}).toThrow('ReactDOMServer does not yet support Suspense.');
|
||||
});
|
||||
}
|
||||
|
||||
it('does not get confused by throwing null', () => {
|
||||
function Bad() {
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
|
||||
+11
-14
@@ -63,7 +63,6 @@ import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';
|
||||
|
||||
import {
|
||||
enableClientRenderFallbackOnHydrationMismatch,
|
||||
enableSuspenseServerRenderer,
|
||||
enableCreateEventHandleAPI,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
@@ -747,19 +746,17 @@ function getNextHydratable(node) {
|
||||
if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) {
|
||||
break;
|
||||
}
|
||||
if (enableSuspenseServerRenderer) {
|
||||
if (nodeType === COMMENT_NODE) {
|
||||
const nodeData = (node: any).data;
|
||||
if (
|
||||
nodeData === SUSPENSE_START_DATA ||
|
||||
nodeData === SUSPENSE_FALLBACK_START_DATA ||
|
||||
nodeData === SUSPENSE_PENDING_START_DATA
|
||||
) {
|
||||
break;
|
||||
}
|
||||
if (nodeData === SUSPENSE_END_DATA) {
|
||||
return null;
|
||||
}
|
||||
if (nodeType === COMMENT_NODE) {
|
||||
const nodeData = (node: any).data;
|
||||
if (
|
||||
nodeData === SUSPENSE_START_DATA ||
|
||||
nodeData === SUSPENSE_FALLBACK_START_DATA ||
|
||||
nodeData === SUSPENSE_PENDING_START_DATA
|
||||
) {
|
||||
break;
|
||||
}
|
||||
if (nodeData === SUSPENSE_END_DATA) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+38
-47
@@ -21,7 +21,6 @@ import {
|
||||
warnAboutDeprecatedLifecycles,
|
||||
disableLegacyContext,
|
||||
disableModulePatternComponents,
|
||||
enableSuspenseServerRenderer,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
@@ -965,21 +964,17 @@ class ReactDOMServerRenderer {
|
||||
outBuffer += this.render(child, frame.context, frame.domNamespace);
|
||||
} catch (err) {
|
||||
if (err != null && typeof err.then === 'function') {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
if (this.suspenseDepth <= 0) {
|
||||
throw new Error(
|
||||
// TODO: include component name. This is a bit tricky with current factoring.
|
||||
'A React component suspended while rendering, but no fallback UI was specified.\n' +
|
||||
'\n' +
|
||||
'Add a <Suspense fallback=...> component higher in the tree to ' +
|
||||
'provide a loading indicator or placeholder to display.',
|
||||
);
|
||||
}
|
||||
|
||||
suspended = true;
|
||||
} else {
|
||||
throw new Error('ReactDOMServer does not yet support Suspense.');
|
||||
if (this.suspenseDepth <= 0) {
|
||||
throw new Error(
|
||||
// TODO: include component name. This is a bit tricky with current factoring.
|
||||
'A React component suspended while rendering, but no fallback UI was specified.\n' +
|
||||
'\n' +
|
||||
'Add a <Suspense fallback=...> component higher in the tree to ' +
|
||||
'provide a loading indicator or placeholder to display.',
|
||||
);
|
||||
}
|
||||
|
||||
suspended = true;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
@@ -1097,39 +1092,35 @@ class ReactDOMServerRenderer {
|
||||
return '';
|
||||
}
|
||||
case REACT_SUSPENSE_TYPE: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const fallback = ((nextChild: any): ReactElement).props.fallback;
|
||||
const fallbackChildren = toArray(fallback);
|
||||
const nextChildren = toArray(
|
||||
((nextChild: any): ReactElement).props.children,
|
||||
);
|
||||
const fallbackFrame: Frame = {
|
||||
type: null,
|
||||
domNamespace: parentNamespace,
|
||||
children: fallbackChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '<!--/$-->',
|
||||
};
|
||||
const frame: Frame = {
|
||||
fallbackFrame,
|
||||
type: REACT_SUSPENSE_TYPE,
|
||||
domNamespace: parentNamespace,
|
||||
children: nextChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '<!--/$-->',
|
||||
};
|
||||
if (__DEV__) {
|
||||
((frame: any): FrameDev).debugElementStack = [];
|
||||
((fallbackFrame: any): FrameDev).debugElementStack = [];
|
||||
}
|
||||
this.stack.push(frame);
|
||||
this.suspenseDepth++;
|
||||
return '<!--$-->';
|
||||
} else {
|
||||
throw new Error('ReactDOMServer does not yet support Suspense.');
|
||||
const fallback = ((nextChild: any): ReactElement).props.fallback;
|
||||
const fallbackChildren = toArray(fallback);
|
||||
const nextChildren = toArray(
|
||||
((nextChild: any): ReactElement).props.children,
|
||||
);
|
||||
const fallbackFrame: Frame = {
|
||||
type: null,
|
||||
domNamespace: parentNamespace,
|
||||
children: fallbackChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '<!--/$-->',
|
||||
};
|
||||
const frame: Frame = {
|
||||
fallbackFrame,
|
||||
type: REACT_SUSPENSE_TYPE,
|
||||
domNamespace: parentNamespace,
|
||||
children: nextChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '<!--/$-->',
|
||||
};
|
||||
if (__DEV__) {
|
||||
((frame: any): FrameDev).debugElementStack = [];
|
||||
((fallbackFrame: any): FrameDev).debugElementStack = [];
|
||||
}
|
||||
this.stack.push(frame);
|
||||
this.suspenseDepth++;
|
||||
return '<!--$-->';
|
||||
}
|
||||
// eslint-disable-next-line-no-fallthrough
|
||||
case REACT_SCOPE_TYPE: {
|
||||
|
||||
@@ -96,7 +96,6 @@ import {
|
||||
disableModulePatternComponents,
|
||||
enableProfilerCommitHooks,
|
||||
enableProfilerTimer,
|
||||
enableSuspenseServerRenderer,
|
||||
warnAboutDefaultPropsOnFunctionComponents,
|
||||
enableScopeAPI,
|
||||
enableCache,
|
||||
@@ -2134,17 +2133,15 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
||||
// If we're currently hydrating, try to hydrate this boundary.
|
||||
tryToClaimNextHydratableInstance(workInProgress);
|
||||
// This could've been a dehydrated suspense component.
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
||||
if (suspenseState !== null) {
|
||||
const dehydrated = suspenseState.dehydrated;
|
||||
if (dehydrated !== null) {
|
||||
return mountDehydratedSuspenseComponent(
|
||||
workInProgress,
|
||||
dehydrated,
|
||||
renderLanes,
|
||||
);
|
||||
}
|
||||
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
||||
if (suspenseState !== null) {
|
||||
const dehydrated = suspenseState.dehydrated;
|
||||
if (dehydrated !== null) {
|
||||
return mountDehydratedSuspenseComponent(
|
||||
workInProgress,
|
||||
dehydrated,
|
||||
renderLanes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2220,59 +2217,57 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
||||
// The current tree is already showing a fallback
|
||||
|
||||
// Special path for hydration
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const dehydrated = prevState.dehydrated;
|
||||
if (dehydrated !== null) {
|
||||
if (!didSuspend) {
|
||||
return updateDehydratedSuspenseComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
dehydrated,
|
||||
prevState,
|
||||
renderLanes,
|
||||
);
|
||||
} else if (workInProgress.flags & ForceClientRender) {
|
||||
// Something errored during hydration. Try again without hydrating.
|
||||
workInProgress.flags &= ~ForceClientRender;
|
||||
return retrySuspenseComponentWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
renderLanes,
|
||||
new Error(
|
||||
'There was an error while hydrating this Suspense boundary. ' +
|
||||
'Switched to client rendering.',
|
||||
),
|
||||
);
|
||||
} else if (
|
||||
(workInProgress.memoizedState: null | SuspenseState) !== null
|
||||
) {
|
||||
// Something suspended and we should still be in dehydrated mode.
|
||||
// Leave the existing child in place.
|
||||
workInProgress.child = current.child;
|
||||
// The dehydrated completion pass expects this flag to be there
|
||||
// but the normal suspense pass doesn't.
|
||||
workInProgress.flags |= DidCapture;
|
||||
return null;
|
||||
} else {
|
||||
// Suspended but we should no longer be in dehydrated mode.
|
||||
// Therefore we now have to render the fallback.
|
||||
renderDidSuspendDelayIfPossible();
|
||||
const nextPrimaryChildren = nextProps.children;
|
||||
const nextFallbackChildren = nextProps.fallback;
|
||||
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
nextPrimaryChildren,
|
||||
nextFallbackChildren,
|
||||
renderLanes,
|
||||
);
|
||||
const primaryChildFragment: Fiber = (workInProgress.child: any);
|
||||
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
|
||||
renderLanes,
|
||||
);
|
||||
workInProgress.memoizedState = SUSPENDED_MARKER;
|
||||
return fallbackChildFragment;
|
||||
}
|
||||
const dehydrated = prevState.dehydrated;
|
||||
if (dehydrated !== null) {
|
||||
if (!didSuspend) {
|
||||
return updateDehydratedSuspenseComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
dehydrated,
|
||||
prevState,
|
||||
renderLanes,
|
||||
);
|
||||
} else if (workInProgress.flags & ForceClientRender) {
|
||||
// Something errored during hydration. Try again without hydrating.
|
||||
workInProgress.flags &= ~ForceClientRender;
|
||||
return retrySuspenseComponentWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
renderLanes,
|
||||
new Error(
|
||||
'There was an error while hydrating this Suspense boundary. ' +
|
||||
'Switched to client rendering.',
|
||||
),
|
||||
);
|
||||
} else if (
|
||||
(workInProgress.memoizedState: null | SuspenseState) !== null
|
||||
) {
|
||||
// Something suspended and we should still be in dehydrated mode.
|
||||
// Leave the existing child in place.
|
||||
workInProgress.child = current.child;
|
||||
// The dehydrated completion pass expects this flag to be there
|
||||
// but the normal suspense pass doesn't.
|
||||
workInProgress.flags |= DidCapture;
|
||||
return null;
|
||||
} else {
|
||||
// Suspended but we should no longer be in dehydrated mode.
|
||||
// Therefore we now have to render the fallback.
|
||||
renderDidSuspendDelayIfPossible();
|
||||
const nextPrimaryChildren = nextProps.children;
|
||||
const nextFallbackChildren = nextProps.fallback;
|
||||
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
nextPrimaryChildren,
|
||||
nextFallbackChildren,
|
||||
renderLanes,
|
||||
);
|
||||
const primaryChildFragment: Fiber = (workInProgress.child: any);
|
||||
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
|
||||
renderLanes,
|
||||
);
|
||||
workInProgress.memoizedState = SUSPENDED_MARKER;
|
||||
return fallbackChildFragment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3657,20 +3652,18 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
|
||||
case SuspenseComponent: {
|
||||
const state: SuspenseState | null = workInProgress.memoizedState;
|
||||
if (state !== null) {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
if (state.dehydrated !== null) {
|
||||
pushSuspenseContext(
|
||||
workInProgress,
|
||||
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
|
||||
);
|
||||
// We know that this component will suspend again because if it has
|
||||
// been unsuspended it has committed as a resolved Suspense component.
|
||||
// If it needs to be retried, it should have work scheduled on it.
|
||||
workInProgress.flags |= DidCapture;
|
||||
// We should never render the children of a dehydrated boundary until we
|
||||
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
|
||||
return null;
|
||||
}
|
||||
if (state.dehydrated !== null) {
|
||||
pushSuspenseContext(
|
||||
workInProgress,
|
||||
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
|
||||
);
|
||||
// We know that this component will suspend again because if it has
|
||||
// been unsuspended it has committed as a resolved Suspense component.
|
||||
// If it needs to be retried, it should have work scheduled on it.
|
||||
workInProgress.flags |= DidCapture;
|
||||
// We should never render the children of a dehydrated boundary until we
|
||||
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
|
||||
return null;
|
||||
}
|
||||
|
||||
// If this boundary is currently timed out, we need to decide
|
||||
|
||||
@@ -96,7 +96,6 @@ import {
|
||||
disableModulePatternComponents,
|
||||
enableProfilerCommitHooks,
|
||||
enableProfilerTimer,
|
||||
enableSuspenseServerRenderer,
|
||||
warnAboutDefaultPropsOnFunctionComponents,
|
||||
enableScopeAPI,
|
||||
enableCache,
|
||||
@@ -2134,17 +2133,15 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
||||
// If we're currently hydrating, try to hydrate this boundary.
|
||||
tryToClaimNextHydratableInstance(workInProgress);
|
||||
// This could've been a dehydrated suspense component.
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
||||
if (suspenseState !== null) {
|
||||
const dehydrated = suspenseState.dehydrated;
|
||||
if (dehydrated !== null) {
|
||||
return mountDehydratedSuspenseComponent(
|
||||
workInProgress,
|
||||
dehydrated,
|
||||
renderLanes,
|
||||
);
|
||||
}
|
||||
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
||||
if (suspenseState !== null) {
|
||||
const dehydrated = suspenseState.dehydrated;
|
||||
if (dehydrated !== null) {
|
||||
return mountDehydratedSuspenseComponent(
|
||||
workInProgress,
|
||||
dehydrated,
|
||||
renderLanes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2220,59 +2217,57 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
||||
// The current tree is already showing a fallback
|
||||
|
||||
// Special path for hydration
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const dehydrated = prevState.dehydrated;
|
||||
if (dehydrated !== null) {
|
||||
if (!didSuspend) {
|
||||
return updateDehydratedSuspenseComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
dehydrated,
|
||||
prevState,
|
||||
renderLanes,
|
||||
);
|
||||
} else if (workInProgress.flags & ForceClientRender) {
|
||||
// Something errored during hydration. Try again without hydrating.
|
||||
workInProgress.flags &= ~ForceClientRender;
|
||||
return retrySuspenseComponentWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
renderLanes,
|
||||
new Error(
|
||||
'There was an error while hydrating this Suspense boundary. ' +
|
||||
'Switched to client rendering.',
|
||||
),
|
||||
);
|
||||
} else if (
|
||||
(workInProgress.memoizedState: null | SuspenseState) !== null
|
||||
) {
|
||||
// Something suspended and we should still be in dehydrated mode.
|
||||
// Leave the existing child in place.
|
||||
workInProgress.child = current.child;
|
||||
// The dehydrated completion pass expects this flag to be there
|
||||
// but the normal suspense pass doesn't.
|
||||
workInProgress.flags |= DidCapture;
|
||||
return null;
|
||||
} else {
|
||||
// Suspended but we should no longer be in dehydrated mode.
|
||||
// Therefore we now have to render the fallback.
|
||||
renderDidSuspendDelayIfPossible();
|
||||
const nextPrimaryChildren = nextProps.children;
|
||||
const nextFallbackChildren = nextProps.fallback;
|
||||
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
nextPrimaryChildren,
|
||||
nextFallbackChildren,
|
||||
renderLanes,
|
||||
);
|
||||
const primaryChildFragment: Fiber = (workInProgress.child: any);
|
||||
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
|
||||
renderLanes,
|
||||
);
|
||||
workInProgress.memoizedState = SUSPENDED_MARKER;
|
||||
return fallbackChildFragment;
|
||||
}
|
||||
const dehydrated = prevState.dehydrated;
|
||||
if (dehydrated !== null) {
|
||||
if (!didSuspend) {
|
||||
return updateDehydratedSuspenseComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
dehydrated,
|
||||
prevState,
|
||||
renderLanes,
|
||||
);
|
||||
} else if (workInProgress.flags & ForceClientRender) {
|
||||
// Something errored during hydration. Try again without hydrating.
|
||||
workInProgress.flags &= ~ForceClientRender;
|
||||
return retrySuspenseComponentWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
renderLanes,
|
||||
new Error(
|
||||
'There was an error while hydrating this Suspense boundary. ' +
|
||||
'Switched to client rendering.',
|
||||
),
|
||||
);
|
||||
} else if (
|
||||
(workInProgress.memoizedState: null | SuspenseState) !== null
|
||||
) {
|
||||
// Something suspended and we should still be in dehydrated mode.
|
||||
// Leave the existing child in place.
|
||||
workInProgress.child = current.child;
|
||||
// The dehydrated completion pass expects this flag to be there
|
||||
// but the normal suspense pass doesn't.
|
||||
workInProgress.flags |= DidCapture;
|
||||
return null;
|
||||
} else {
|
||||
// Suspended but we should no longer be in dehydrated mode.
|
||||
// Therefore we now have to render the fallback.
|
||||
renderDidSuspendDelayIfPossible();
|
||||
const nextPrimaryChildren = nextProps.children;
|
||||
const nextFallbackChildren = nextProps.fallback;
|
||||
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
|
||||
current,
|
||||
workInProgress,
|
||||
nextPrimaryChildren,
|
||||
nextFallbackChildren,
|
||||
renderLanes,
|
||||
);
|
||||
const primaryChildFragment: Fiber = (workInProgress.child: any);
|
||||
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
|
||||
renderLanes,
|
||||
);
|
||||
workInProgress.memoizedState = SUSPENDED_MARKER;
|
||||
return fallbackChildFragment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3657,20 +3652,18 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
|
||||
case SuspenseComponent: {
|
||||
const state: SuspenseState | null = workInProgress.memoizedState;
|
||||
if (state !== null) {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
if (state.dehydrated !== null) {
|
||||
pushSuspenseContext(
|
||||
workInProgress,
|
||||
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
|
||||
);
|
||||
// We know that this component will suspend again because if it has
|
||||
// been unsuspended it has committed as a resolved Suspense component.
|
||||
// If it needs to be retried, it should have work scheduled on it.
|
||||
workInProgress.flags |= DidCapture;
|
||||
// We should never render the children of a dehydrated boundary until we
|
||||
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
|
||||
return null;
|
||||
}
|
||||
if (state.dehydrated !== null) {
|
||||
pushSuspenseContext(
|
||||
workInProgress,
|
||||
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
|
||||
);
|
||||
// We know that this component will suspend again because if it has
|
||||
// been unsuspended it has committed as a resolved Suspense component.
|
||||
// If it needs to be retried, it should have work scheduled on it.
|
||||
workInProgress.flags |= DidCapture;
|
||||
// We should never render the children of a dehydrated boundary until we
|
||||
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
|
||||
return null;
|
||||
}
|
||||
|
||||
// If this boundary is currently timed out, we need to decide
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
enableProfilerCommitHooks,
|
||||
enableProfilerNestedUpdatePhase,
|
||||
enableSchedulingProfiler,
|
||||
enableSuspenseServerRenderer,
|
||||
enableSuspenseCallback,
|
||||
enableScopeAPI,
|
||||
enableStrictEffects,
|
||||
@@ -1652,33 +1651,31 @@ function commitDeletionEffectsOnFiber(
|
||||
return;
|
||||
}
|
||||
case DehydratedFragment: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
if (enableSuspenseCallback) {
|
||||
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
|
||||
if (hydrationCallbacks !== null) {
|
||||
const onDeleted = hydrationCallbacks.onDeleted;
|
||||
if (onDeleted) {
|
||||
onDeleted((deletedFiber.stateNode: SuspenseInstance));
|
||||
}
|
||||
if (enableSuspenseCallback) {
|
||||
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
|
||||
if (hydrationCallbacks !== null) {
|
||||
const onDeleted = hydrationCallbacks.onDeleted;
|
||||
if (onDeleted) {
|
||||
onDeleted((deletedFiber.stateNode: SuspenseInstance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dehydrated fragments don't have any children
|
||||
// Dehydrated fragments don't have any children
|
||||
|
||||
// Delete the dehydrated suspense boundary and all of its content.
|
||||
if (supportsMutation) {
|
||||
if (hostParent !== null) {
|
||||
if (hostParentIsContainer) {
|
||||
clearSuspenseBoundaryFromContainer(
|
||||
((hostParent: any): Container),
|
||||
(deletedFiber.stateNode: SuspenseInstance),
|
||||
);
|
||||
} else {
|
||||
clearSuspenseBoundary(
|
||||
((hostParent: any): Instance),
|
||||
(deletedFiber.stateNode: SuspenseInstance),
|
||||
);
|
||||
}
|
||||
// Delete the dehydrated suspense boundary and all of its content.
|
||||
if (supportsMutation) {
|
||||
if (hostParent !== null) {
|
||||
if (hostParentIsContainer) {
|
||||
clearSuspenseBoundaryFromContainer(
|
||||
((hostParent: any): Container),
|
||||
(deletedFiber.stateNode: SuspenseInstance),
|
||||
);
|
||||
} else {
|
||||
clearSuspenseBoundary(
|
||||
((hostParent: any): Instance),
|
||||
(deletedFiber.stateNode: SuspenseInstance),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
enableProfilerCommitHooks,
|
||||
enableProfilerNestedUpdatePhase,
|
||||
enableSchedulingProfiler,
|
||||
enableSuspenseServerRenderer,
|
||||
enableSuspenseCallback,
|
||||
enableScopeAPI,
|
||||
enableStrictEffects,
|
||||
@@ -1652,33 +1651,31 @@ function commitDeletionEffectsOnFiber(
|
||||
return;
|
||||
}
|
||||
case DehydratedFragment: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
if (enableSuspenseCallback) {
|
||||
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
|
||||
if (hydrationCallbacks !== null) {
|
||||
const onDeleted = hydrationCallbacks.onDeleted;
|
||||
if (onDeleted) {
|
||||
onDeleted((deletedFiber.stateNode: SuspenseInstance));
|
||||
}
|
||||
if (enableSuspenseCallback) {
|
||||
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
|
||||
if (hydrationCallbacks !== null) {
|
||||
const onDeleted = hydrationCallbacks.onDeleted;
|
||||
if (onDeleted) {
|
||||
onDeleted((deletedFiber.stateNode: SuspenseInstance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dehydrated fragments don't have any children
|
||||
// Dehydrated fragments don't have any children
|
||||
|
||||
// Delete the dehydrated suspense boundary and all of its content.
|
||||
if (supportsMutation) {
|
||||
if (hostParent !== null) {
|
||||
if (hostParentIsContainer) {
|
||||
clearSuspenseBoundaryFromContainer(
|
||||
((hostParent: any): Container),
|
||||
(deletedFiber.stateNode: SuspenseInstance),
|
||||
);
|
||||
} else {
|
||||
clearSuspenseBoundary(
|
||||
((hostParent: any): Instance),
|
||||
(deletedFiber.stateNode: SuspenseInstance),
|
||||
);
|
||||
}
|
||||
// Delete the dehydrated suspense boundary and all of its content.
|
||||
if (supportsMutation) {
|
||||
if (hostParent !== null) {
|
||||
if (hostParentIsContainer) {
|
||||
clearSuspenseBoundaryFromContainer(
|
||||
((hostParent: any): Container),
|
||||
(deletedFiber.stateNode: SuspenseInstance),
|
||||
);
|
||||
} else {
|
||||
clearSuspenseBoundary(
|
||||
((hostParent: any): Instance),
|
||||
(deletedFiber.stateNode: SuspenseInstance),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,6 @@ import {
|
||||
} from './ReactFiberHydrationContext.new';
|
||||
import {
|
||||
enableSuspenseCallback,
|
||||
enableSuspenseServerRenderer,
|
||||
enableScopeAPI,
|
||||
enableProfilerTimer,
|
||||
enableCache,
|
||||
@@ -1066,84 +1065,81 @@ function completeWork(
|
||||
popSuspenseContext(workInProgress);
|
||||
const nextState: null | SuspenseState = workInProgress.memoizedState;
|
||||
|
||||
if (enableSuspenseServerRenderer) {
|
||||
if (
|
||||
enableClientRenderFallbackOnHydrationMismatch &&
|
||||
hasUnhydratedTailNodes() &&
|
||||
(workInProgress.mode & ConcurrentMode) !== NoMode &&
|
||||
(workInProgress.flags & DidCapture) === NoFlags
|
||||
) {
|
||||
warnIfUnhydratedTailNodes(workInProgress);
|
||||
resetHydrationState();
|
||||
workInProgress.flags |=
|
||||
ForceClientRender | Incomplete | ShouldCapture;
|
||||
return workInProgress;
|
||||
}
|
||||
if (nextState !== null && nextState.dehydrated !== null) {
|
||||
// We might be inside a hydration state the first time we're picking up this
|
||||
// Suspense boundary, and also after we've reentered it for further hydration.
|
||||
const wasHydrated = popHydrationState(workInProgress);
|
||||
if (current === null) {
|
||||
if (!wasHydrated) {
|
||||
throw new Error(
|
||||
'A dehydrated suspense component was completed without a hydrated node. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
}
|
||||
prepareToHydrateHostSuspenseInstance(workInProgress);
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// We might have reentered this boundary to hydrate it. If so, we need to reset the hydration
|
||||
// state since we're now exiting out of it. popHydrationState doesn't do that for us.
|
||||
resetHydrationState();
|
||||
if ((workInProgress.flags & DidCapture) === NoFlags) {
|
||||
// This boundary did not suspend so it's now hydrated and unsuspended.
|
||||
workInProgress.memoizedState = null;
|
||||
}
|
||||
// If nothing suspended, we need to schedule an effect to mark this boundary
|
||||
// as having hydrated so events know that they're free to be invoked.
|
||||
// It's also a signal to replay events and the suspense callback.
|
||||
// If something suspended, schedule an effect to attach retry listeners.
|
||||
// So we might as well always mark this.
|
||||
workInProgress.flags |= Update;
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Successfully completed this tree. 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();
|
||||
if (
|
||||
enableClientRenderFallbackOnHydrationMismatch &&
|
||||
hasUnhydratedTailNodes() &&
|
||||
(workInProgress.mode & ConcurrentMode) !== NoMode &&
|
||||
(workInProgress.flags & DidCapture) === NoFlags
|
||||
) {
|
||||
warnIfUnhydratedTailNodes(workInProgress);
|
||||
resetHydrationState();
|
||||
workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture;
|
||||
return workInProgress;
|
||||
}
|
||||
if (nextState !== null && nextState.dehydrated !== null) {
|
||||
// We might be inside a hydration state the first time we're picking up this
|
||||
// Suspense boundary, and also after we've reentered it for further hydration.
|
||||
const wasHydrated = popHydrationState(workInProgress);
|
||||
if (current === null) {
|
||||
if (!wasHydrated) {
|
||||
throw new Error(
|
||||
'A dehydrated suspense component was completed without a hydrated node. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
}
|
||||
prepareToHydrateHostSuspenseInstance(workInProgress);
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// We might have reentered this boundary to hydrate it. If so, we need to reset the hydration
|
||||
// state since we're now exiting out of it. popHydrationState doesn't do that for us.
|
||||
resetHydrationState();
|
||||
if ((workInProgress.flags & DidCapture) === NoFlags) {
|
||||
// This boundary did not suspend so it's now hydrated and unsuspended.
|
||||
workInProgress.memoizedState = null;
|
||||
}
|
||||
// If nothing suspended, we need to schedule an effect to mark this boundary
|
||||
// as having hydrated so events know that they're free to be invoked.
|
||||
// It's also a signal to replay events and the suspense callback.
|
||||
// If something suspended, schedule an effect to attach retry listeners.
|
||||
// So we might as well always mark this.
|
||||
workInProgress.flags |= Update;
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Successfully completed this tree. 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();
|
||||
|
||||
if ((workInProgress.flags & DidCapture) !== NoFlags) {
|
||||
// Something suspended. Re-render with the fallback children.
|
||||
|
||||
@@ -138,7 +138,6 @@ import {
|
||||
} from './ReactFiberHydrationContext.old';
|
||||
import {
|
||||
enableSuspenseCallback,
|
||||
enableSuspenseServerRenderer,
|
||||
enableScopeAPI,
|
||||
enableProfilerTimer,
|
||||
enableCache,
|
||||
@@ -1066,84 +1065,81 @@ function completeWork(
|
||||
popSuspenseContext(workInProgress);
|
||||
const nextState: null | SuspenseState = workInProgress.memoizedState;
|
||||
|
||||
if (enableSuspenseServerRenderer) {
|
||||
if (
|
||||
enableClientRenderFallbackOnHydrationMismatch &&
|
||||
hasUnhydratedTailNodes() &&
|
||||
(workInProgress.mode & ConcurrentMode) !== NoMode &&
|
||||
(workInProgress.flags & DidCapture) === NoFlags
|
||||
) {
|
||||
warnIfUnhydratedTailNodes(workInProgress);
|
||||
resetHydrationState();
|
||||
workInProgress.flags |=
|
||||
ForceClientRender | Incomplete | ShouldCapture;
|
||||
return workInProgress;
|
||||
}
|
||||
if (nextState !== null && nextState.dehydrated !== null) {
|
||||
// We might be inside a hydration state the first time we're picking up this
|
||||
// Suspense boundary, and also after we've reentered it for further hydration.
|
||||
const wasHydrated = popHydrationState(workInProgress);
|
||||
if (current === null) {
|
||||
if (!wasHydrated) {
|
||||
throw new Error(
|
||||
'A dehydrated suspense component was completed without a hydrated node. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
}
|
||||
prepareToHydrateHostSuspenseInstance(workInProgress);
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// We might have reentered this boundary to hydrate it. If so, we need to reset the hydration
|
||||
// state since we're now exiting out of it. popHydrationState doesn't do that for us.
|
||||
resetHydrationState();
|
||||
if ((workInProgress.flags & DidCapture) === NoFlags) {
|
||||
// This boundary did not suspend so it's now hydrated and unsuspended.
|
||||
workInProgress.memoizedState = null;
|
||||
}
|
||||
// If nothing suspended, we need to schedule an effect to mark this boundary
|
||||
// as having hydrated so events know that they're free to be invoked.
|
||||
// It's also a signal to replay events and the suspense callback.
|
||||
// If something suspended, schedule an effect to attach retry listeners.
|
||||
// So we might as well always mark this.
|
||||
workInProgress.flags |= Update;
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Successfully completed this tree. 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();
|
||||
if (
|
||||
enableClientRenderFallbackOnHydrationMismatch &&
|
||||
hasUnhydratedTailNodes() &&
|
||||
(workInProgress.mode & ConcurrentMode) !== NoMode &&
|
||||
(workInProgress.flags & DidCapture) === NoFlags
|
||||
) {
|
||||
warnIfUnhydratedTailNodes(workInProgress);
|
||||
resetHydrationState();
|
||||
workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture;
|
||||
return workInProgress;
|
||||
}
|
||||
if (nextState !== null && nextState.dehydrated !== null) {
|
||||
// We might be inside a hydration state the first time we're picking up this
|
||||
// Suspense boundary, and also after we've reentered it for further hydration.
|
||||
const wasHydrated = popHydrationState(workInProgress);
|
||||
if (current === null) {
|
||||
if (!wasHydrated) {
|
||||
throw new Error(
|
||||
'A dehydrated suspense component was completed without a hydrated node. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
}
|
||||
prepareToHydrateHostSuspenseInstance(workInProgress);
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// We might have reentered this boundary to hydrate it. If so, we need to reset the hydration
|
||||
// state since we're now exiting out of it. popHydrationState doesn't do that for us.
|
||||
resetHydrationState();
|
||||
if ((workInProgress.flags & DidCapture) === NoFlags) {
|
||||
// This boundary did not suspend so it's now hydrated and unsuspended.
|
||||
workInProgress.memoizedState = null;
|
||||
}
|
||||
// If nothing suspended, we need to schedule an effect to mark this boundary
|
||||
// as having hydrated so events know that they're free to be invoked.
|
||||
// It's also a signal to replay events and the suspense callback.
|
||||
// If something suspended, schedule an effect to attach retry listeners.
|
||||
// So we might as well always mark this.
|
||||
workInProgress.flags |= Update;
|
||||
bubbleProperties(workInProgress);
|
||||
if (enableProfilerTimer) {
|
||||
if ((workInProgress.mode & ProfileMode) !== NoMode) {
|
||||
const isTimedOutSuspense = nextState !== null;
|
||||
if (isTimedOutSuspense) {
|
||||
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
|
||||
const primaryChildFragment = workInProgress.child;
|
||||
if (primaryChildFragment !== null) {
|
||||
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator
|
||||
workInProgress.treeBaseDuration -= ((primaryChildFragment.treeBaseDuration: any): number);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Successfully completed this tree. 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();
|
||||
|
||||
if ((workInProgress.flags & DidCapture) !== NoFlags) {
|
||||
// Something suspended. Re-render with the fallback children.
|
||||
|
||||
@@ -68,10 +68,7 @@ import {
|
||||
didNotFindHydratableTextInstance,
|
||||
didNotFindHydratableSuspenseInstance,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {
|
||||
enableClientRenderFallbackOnHydrationMismatch,
|
||||
enableSuspenseServerRenderer,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {enableClientRenderFallbackOnHydrationMismatch} from 'shared/ReactFeatureFlags';
|
||||
import {OffscreenLane} from './ReactFiberLane.new';
|
||||
import {
|
||||
getSuspendedTreeContext,
|
||||
@@ -347,32 +344,30 @@ function tryHydrate(fiber, nextInstance) {
|
||||
return false;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const suspenseInstance: null | SuspenseInstance = canHydrateSuspenseInstance(
|
||||
nextInstance,
|
||||
const suspenseInstance: null | SuspenseInstance = canHydrateSuspenseInstance(
|
||||
nextInstance,
|
||||
);
|
||||
if (suspenseInstance !== null) {
|
||||
const suspenseState: SuspenseState = {
|
||||
dehydrated: suspenseInstance,
|
||||
treeContext: getSuspendedTreeContext(),
|
||||
retryLane: OffscreenLane,
|
||||
};
|
||||
fiber.memoizedState = suspenseState;
|
||||
// Store the dehydrated fragment as a child fiber.
|
||||
// This simplifies the code for getHostSibling and deleting nodes,
|
||||
// since it doesn't have to consider all Suspense boundaries and
|
||||
// check if they're dehydrated ones or not.
|
||||
const dehydratedFragment = createFiberFromDehydratedFragment(
|
||||
suspenseInstance,
|
||||
);
|
||||
if (suspenseInstance !== null) {
|
||||
const suspenseState: SuspenseState = {
|
||||
dehydrated: suspenseInstance,
|
||||
treeContext: getSuspendedTreeContext(),
|
||||
retryLane: OffscreenLane,
|
||||
};
|
||||
fiber.memoizedState = suspenseState;
|
||||
// Store the dehydrated fragment as a child fiber.
|
||||
// This simplifies the code for getHostSibling and deleting nodes,
|
||||
// since it doesn't have to consider all Suspense boundaries and
|
||||
// check if they're dehydrated ones or not.
|
||||
const dehydratedFragment = createFiberFromDehydratedFragment(
|
||||
suspenseInstance,
|
||||
);
|
||||
dehydratedFragment.return = fiber;
|
||||
fiber.child = dehydratedFragment;
|
||||
hydrationParentFiber = fiber;
|
||||
// While a Suspense Instance does have children, we won't step into
|
||||
// it during the first pass. Instead, we'll reenter it later.
|
||||
nextHydratableInstance = null;
|
||||
return true;
|
||||
}
|
||||
dehydratedFragment.return = fiber;
|
||||
fiber.child = dehydratedFragment;
|
||||
hydrationParentFiber = fiber;
|
||||
// While a Suspense Instance does have children, we won't step into
|
||||
// it during the first pass. Instead, we'll reenter it later.
|
||||
nextHydratableInstance = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -68,10 +68,7 @@ import {
|
||||
didNotFindHydratableTextInstance,
|
||||
didNotFindHydratableSuspenseInstance,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {
|
||||
enableClientRenderFallbackOnHydrationMismatch,
|
||||
enableSuspenseServerRenderer,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {enableClientRenderFallbackOnHydrationMismatch} from 'shared/ReactFeatureFlags';
|
||||
import {OffscreenLane} from './ReactFiberLane.old';
|
||||
import {
|
||||
getSuspendedTreeContext,
|
||||
@@ -347,32 +344,30 @@ function tryHydrate(fiber, nextInstance) {
|
||||
return false;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const suspenseInstance: null | SuspenseInstance = canHydrateSuspenseInstance(
|
||||
nextInstance,
|
||||
const suspenseInstance: null | SuspenseInstance = canHydrateSuspenseInstance(
|
||||
nextInstance,
|
||||
);
|
||||
if (suspenseInstance !== null) {
|
||||
const suspenseState: SuspenseState = {
|
||||
dehydrated: suspenseInstance,
|
||||
treeContext: getSuspendedTreeContext(),
|
||||
retryLane: OffscreenLane,
|
||||
};
|
||||
fiber.memoizedState = suspenseState;
|
||||
// Store the dehydrated fragment as a child fiber.
|
||||
// This simplifies the code for getHostSibling and deleting nodes,
|
||||
// since it doesn't have to consider all Suspense boundaries and
|
||||
// check if they're dehydrated ones or not.
|
||||
const dehydratedFragment = createFiberFromDehydratedFragment(
|
||||
suspenseInstance,
|
||||
);
|
||||
if (suspenseInstance !== null) {
|
||||
const suspenseState: SuspenseState = {
|
||||
dehydrated: suspenseInstance,
|
||||
treeContext: getSuspendedTreeContext(),
|
||||
retryLane: OffscreenLane,
|
||||
};
|
||||
fiber.memoizedState = suspenseState;
|
||||
// Store the dehydrated fragment as a child fiber.
|
||||
// This simplifies the code for getHostSibling and deleting nodes,
|
||||
// since it doesn't have to consider all Suspense boundaries and
|
||||
// check if they're dehydrated ones or not.
|
||||
const dehydratedFragment = createFiberFromDehydratedFragment(
|
||||
suspenseInstance,
|
||||
);
|
||||
dehydratedFragment.return = fiber;
|
||||
fiber.child = dehydratedFragment;
|
||||
hydrationParentFiber = fiber;
|
||||
// While a Suspense Instance does have children, we won't step into
|
||||
// it during the first pass. Instead, we'll reenter it later.
|
||||
nextHydratableInstance = null;
|
||||
return true;
|
||||
}
|
||||
dehydratedFragment.return = fiber;
|
||||
fiber.child = dehydratedFragment;
|
||||
hydrationParentFiber = fiber;
|
||||
// While a Suspense Instance does have children, we won't step into
|
||||
// it during the first pass. Instead, we'll reenter it later.
|
||||
nextHydratableInstance = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ import is from 'shared/objectIs';
|
||||
import {createUpdate, ForceUpdate} from './ReactUpdateQueue.new';
|
||||
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new';
|
||||
import {
|
||||
enableSuspenseServerRenderer,
|
||||
enableLazyContextPropagation,
|
||||
enableServerContext,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
@@ -295,10 +294,7 @@ function propagateContextChange_eager<T>(
|
||||
} else if (fiber.tag === ContextProvider) {
|
||||
// Don't scan deeper if this is a matching provider
|
||||
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
|
||||
} else if (
|
||||
enableSuspenseServerRenderer &&
|
||||
fiber.tag === DehydratedFragment
|
||||
) {
|
||||
} else if (fiber.tag === DehydratedFragment) {
|
||||
// If a dehydrated suspense boundary is in this subtree, we don't know
|
||||
// if it will have any context consumers in it. The best we can do is
|
||||
// mark it as having updates.
|
||||
@@ -425,10 +421,7 @@ function propagateContextChanges<T>(
|
||||
}
|
||||
dep = dependency.next;
|
||||
}
|
||||
} else if (
|
||||
enableSuspenseServerRenderer &&
|
||||
fiber.tag === DehydratedFragment
|
||||
) {
|
||||
} else if (fiber.tag === DehydratedFragment) {
|
||||
// If a dehydrated suspense boundary is in this subtree, we don't know
|
||||
// if it will have any context consumers in it. The best we can do is
|
||||
// mark it as having updates.
|
||||
|
||||
@@ -42,7 +42,6 @@ import is from 'shared/objectIs';
|
||||
import {createUpdate, ForceUpdate} from './ReactUpdateQueue.old';
|
||||
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.old';
|
||||
import {
|
||||
enableSuspenseServerRenderer,
|
||||
enableLazyContextPropagation,
|
||||
enableServerContext,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
@@ -295,10 +294,7 @@ function propagateContextChange_eager<T>(
|
||||
} else if (fiber.tag === ContextProvider) {
|
||||
// Don't scan deeper if this is a matching provider
|
||||
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
|
||||
} else if (
|
||||
enableSuspenseServerRenderer &&
|
||||
fiber.tag === DehydratedFragment
|
||||
) {
|
||||
} else if (fiber.tag === DehydratedFragment) {
|
||||
// If a dehydrated suspense boundary is in this subtree, we don't know
|
||||
// if it will have any context consumers in it. The best we can do is
|
||||
// mark it as having updates.
|
||||
@@ -425,10 +421,7 @@ function propagateContextChanges<T>(
|
||||
}
|
||||
dep = dependency.next;
|
||||
}
|
||||
} else if (
|
||||
enableSuspenseServerRenderer &&
|
||||
fiber.tag === DehydratedFragment
|
||||
) {
|
||||
} else if (fiber.tag === DehydratedFragment) {
|
||||
// If a dehydrated suspense boundary is in this subtree, we don't know
|
||||
// if it will have any context consumers in it. The best we can do is
|
||||
// mark it as having updates.
|
||||
|
||||
@@ -28,11 +28,7 @@ import {
|
||||
} from './ReactWorkTags';
|
||||
import {DidCapture, NoFlags, ShouldCapture} from './ReactFiberFlags';
|
||||
import {NoMode, ProfileMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
enableSuspenseServerRenderer,
|
||||
enableProfilerTimer,
|
||||
enableCache,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {enableProfilerTimer, enableCache} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {popHostContainer, popHostContext} from './ReactFiberHostContext.new';
|
||||
import {popSuspenseContext} from './ReactFiberSuspenseContext.new';
|
||||
@@ -108,20 +104,18 @@ function unwindWork(
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
popSuspenseContext(workInProgress);
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const suspenseState: null | SuspenseState =
|
||||
workInProgress.memoizedState;
|
||||
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
||||
if (workInProgress.alternate === null) {
|
||||
throw new Error(
|
||||
'Threw in newly mounted dehydrated component. This is likely a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
resetHydrationState();
|
||||
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
||||
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
||||
if (workInProgress.alternate === null) {
|
||||
throw new Error(
|
||||
'Threw in newly mounted dehydrated component. This is likely a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
resetHydrationState();
|
||||
}
|
||||
|
||||
const flags = workInProgress.flags;
|
||||
if (flags & ShouldCapture) {
|
||||
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
||||
|
||||
@@ -28,11 +28,7 @@ import {
|
||||
} from './ReactWorkTags';
|
||||
import {DidCapture, NoFlags, ShouldCapture} from './ReactFiberFlags';
|
||||
import {NoMode, ProfileMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
enableSuspenseServerRenderer,
|
||||
enableProfilerTimer,
|
||||
enableCache,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {enableProfilerTimer, enableCache} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {popHostContainer, popHostContext} from './ReactFiberHostContext.old';
|
||||
import {popSuspenseContext} from './ReactFiberSuspenseContext.old';
|
||||
@@ -108,20 +104,18 @@ function unwindWork(
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
popSuspenseContext(workInProgress);
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const suspenseState: null | SuspenseState =
|
||||
workInProgress.memoizedState;
|
||||
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
||||
if (workInProgress.alternate === null) {
|
||||
throw new Error(
|
||||
'Threw in newly mounted dehydrated component. This is likely a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
resetHydrationState();
|
||||
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
||||
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
||||
if (workInProgress.alternate === null) {
|
||||
throw new Error(
|
||||
'Threw in newly mounted dehydrated component. This is likely a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
resetHydrationState();
|
||||
}
|
||||
|
||||
const flags = workInProgress.flags;
|
||||
if (flags & ShouldCapture) {
|
||||
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
||||
|
||||
@@ -23,7 +23,6 @@ import type {
|
||||
|
||||
import {
|
||||
warnAboutDeprecatedLifecycles,
|
||||
enableSuspenseServerRenderer,
|
||||
replayFailedUnitOfWorkWithInvokeGuardedCallback,
|
||||
enableCreateEventHandleAPI,
|
||||
enableProfilerTimer,
|
||||
@@ -2776,26 +2775,22 @@ export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
|
||||
export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
|
||||
let retryLane = NoLane; // Default
|
||||
let retryCache: WeakSet<Wakeable> | Set<Wakeable> | null;
|
||||
if (enableSuspenseServerRenderer) {
|
||||
switch (boundaryFiber.tag) {
|
||||
case SuspenseComponent:
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
|
||||
if (suspenseState !== null) {
|
||||
retryLane = suspenseState.retryLane;
|
||||
}
|
||||
break;
|
||||
case SuspenseListComponent:
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
'Pinged unknown suspense boundary type. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
switch (boundaryFiber.tag) {
|
||||
case SuspenseComponent:
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
|
||||
if (suspenseState !== null) {
|
||||
retryLane = suspenseState.retryLane;
|
||||
}
|
||||
break;
|
||||
case SuspenseListComponent:
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
'Pinged unknown suspense boundary type. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
}
|
||||
|
||||
if (retryCache !== null) {
|
||||
|
||||
@@ -23,7 +23,6 @@ import type {
|
||||
|
||||
import {
|
||||
warnAboutDeprecatedLifecycles,
|
||||
enableSuspenseServerRenderer,
|
||||
replayFailedUnitOfWorkWithInvokeGuardedCallback,
|
||||
enableCreateEventHandleAPI,
|
||||
enableProfilerTimer,
|
||||
@@ -2776,26 +2775,22 @@ export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
|
||||
export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
|
||||
let retryLane = NoLane; // Default
|
||||
let retryCache: WeakSet<Wakeable> | Set<Wakeable> | null;
|
||||
if (enableSuspenseServerRenderer) {
|
||||
switch (boundaryFiber.tag) {
|
||||
case SuspenseComponent:
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
|
||||
if (suspenseState !== null) {
|
||||
retryLane = suspenseState.retryLane;
|
||||
}
|
||||
break;
|
||||
case SuspenseListComponent:
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
'Pinged unknown suspense boundary type. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
switch (boundaryFiber.tag) {
|
||||
case SuspenseComponent:
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
|
||||
if (suspenseState !== null) {
|
||||
retryLane = suspenseState.retryLane;
|
||||
}
|
||||
break;
|
||||
case SuspenseListComponent:
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
'Pinged unknown suspense boundary type. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
}
|
||||
|
||||
if (retryCache !== null) {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
// Flags that can likely be deleted or landed without consequences
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export const enableSuspenseServerRenderer = true;
|
||||
export const enableSelectiveHydration = true;
|
||||
export const warnAboutDeprecatedLifecycles = true;
|
||||
export const enableComponentStackLocations = true;
|
||||
|
||||
@@ -26,7 +26,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
|
||||
export const enableProfilerNestedUpdatePhase = __PROFILE__;
|
||||
export const enableProfilerNestedUpdateScheduledHook = false;
|
||||
export const enableUpdaterTracking = __PROFILE__;
|
||||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableCache = false;
|
||||
export const enableCacheElement = true;
|
||||
|
||||
@@ -20,7 +20,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
|
||||
export const enableProfilerNestedUpdatePhase = __PROFILE__;
|
||||
export const enableProfilerNestedUpdateScheduledHook = false;
|
||||
export const enableUpdaterTracking = __PROFILE__;
|
||||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableCache = false;
|
||||
export const enableCacheElement = false;
|
||||
|
||||
@@ -20,7 +20,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
|
||||
export const enableProfilerNestedUpdatePhase = __PROFILE__;
|
||||
export const enableProfilerNestedUpdateScheduledHook = false;
|
||||
export const enableUpdaterTracking = false;
|
||||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableCache = __EXPERIMENTAL__;
|
||||
export const enableCacheElement = __EXPERIMENTAL__;
|
||||
|
||||
@@ -20,7 +20,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
|
||||
export const enableProfilerNestedUpdatePhase = __PROFILE__;
|
||||
export const enableProfilerNestedUpdateScheduledHook = false;
|
||||
export const enableUpdaterTracking = false;
|
||||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableCache = true;
|
||||
export const enableCacheElement = true;
|
||||
|
||||
@@ -20,7 +20,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
|
||||
export const enableProfilerNestedUpdatePhase = __PROFILE__;
|
||||
export const enableProfilerNestedUpdateScheduledHook = false;
|
||||
export const enableUpdaterTracking = false;
|
||||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableCache = true;
|
||||
export const enableCacheElement = true;
|
||||
|
||||
@@ -20,7 +20,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
|
||||
export const enableProfilerNestedUpdatePhase = __PROFILE__;
|
||||
export const enableProfilerNestedUpdateScheduledHook = false;
|
||||
export const enableUpdaterTracking = false;
|
||||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableCache = __EXPERIMENTAL__;
|
||||
export const enableCacheElement = __EXPERIMENTAL__;
|
||||
|
||||
@@ -20,7 +20,6 @@ export const enableProfilerCommitHooks = false;
|
||||
export const enableProfilerNestedUpdatePhase = false;
|
||||
export const enableProfilerNestedUpdateScheduledHook = false;
|
||||
export const enableUpdaterTracking = false;
|
||||
export const enableSuspenseServerRenderer = true;
|
||||
export const enableSelectiveHydration = true;
|
||||
export const enableCache = true;
|
||||
export const enableCacheElement = true;
|
||||
|
||||
@@ -67,7 +67,6 @@ export const disableLegacyContext = __EXPERIMENTAL__;
|
||||
export const warnAboutStringRefs = false;
|
||||
export const warnAboutDefaultPropsOnFunctionComponents = false;
|
||||
export const enableGetInspectorDataForInstanceInProduction = false;
|
||||
export const enableSuspenseServerRenderer = true;
|
||||
export const enableSelectiveHydration = true;
|
||||
|
||||
export const enableCache = true;
|
||||
|
||||
Reference in New Issue
Block a user