Skip to content

Commit

Permalink
Land enableClientRenderFallbackOnTextMismatch flag (#24405)
Browse files Browse the repository at this point in the history
This flag is already enabled on all relevant surfaces. We can remove it.
  • Loading branch information
acdlite authored Apr 20, 2022
1 parent 1e748b4 commit 392808a
Show file tree
Hide file tree
Showing 26 changed files with 473 additions and 605 deletions.
37 changes: 0 additions & 37 deletions packages/react-dom/src/__tests__/ReactServerRendering-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ let React;
let ReactDOMServer;
let PropTypes;
let ReactCurrentDispatcher;
const enableSuspenseServerRenderer = require('shared/ReactFeatureFlags')
.enableSuspenseServerRenderer;

describe('ReactDOMServer', () => {
beforeEach(() => {
Expand Down Expand Up @@ -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
Expand Down
25 changes: 11 additions & 14 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';

import {
enableClientRenderFallbackOnHydrationMismatch,
enableSuspenseServerRenderer,
enableCreateEventHandleAPI,
enableScopeAPI,
} from 'shared/ReactFeatureFlags';
Expand Down Expand Up @@ -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;
}
}
}
Expand Down
85 changes: 38 additions & 47 deletions packages/react-dom/src/server/ReactPartialRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
warnAboutDeprecatedLifecycles,
disableLegacyContext,
disableModulePatternComponents,
enableSuspenseServerRenderer,
enableScopeAPI,
} from 'shared/ReactFeatureFlags';
import {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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: {
Expand Down
151 changes: 72 additions & 79 deletions packages/react-reconciler/src/ReactFiberBeginWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ import {
disableModulePatternComponents,
enableProfilerCommitHooks,
enableProfilerTimer,
enableSuspenseServerRenderer,
warnAboutDefaultPropsOnFunctionComponents,
enableScopeAPI,
enableCache,
Expand Down Expand Up @@ -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,
);
}
}

Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 392808a

Please sign in to comment.