Skip to content

Commit 392808a

Browse files
authored
Land enableClientRenderFallbackOnTextMismatch flag (#24405)
This flag is already enabled on all relevant surfaces. We can remove it.
1 parent 1e748b4 commit 392808a

26 files changed

+473
-605
lines changed

packages/react-dom/src/__tests__/ReactServerRendering-test.js

-37
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ let React;
1414
let ReactDOMServer;
1515
let PropTypes;
1616
let ReactCurrentDispatcher;
17-
const enableSuspenseServerRenderer = require('shared/ReactFeatureFlags')
18-
.enableSuspenseServerRenderer;
1917

2018
describe('ReactDOMServer', () => {
2119
beforeEach(() => {
@@ -678,41 +676,6 @@ describe('ReactDOMServer', () => {
678676
expect(markup).toBe('<div></div>');
679677
});
680678

681-
if (!enableSuspenseServerRenderer) {
682-
it('throws for unsupported types on the server', () => {
683-
expect(() => {
684-
ReactDOMServer.renderToString(<React.Suspense />);
685-
}).toThrow('ReactDOMServer does not yet support Suspense.');
686-
687-
async function fakeImport(result) {
688-
return {default: result};
689-
}
690-
691-
expect(() => {
692-
const LazyFoo = React.lazy(() =>
693-
fakeImport(
694-
new Promise(resolve =>
695-
resolve(function Foo() {
696-
return <div />;
697-
}),
698-
),
699-
),
700-
);
701-
ReactDOMServer.renderToString(<LazyFoo />);
702-
}).toThrow('ReactDOMServer does not yet support Suspense.');
703-
});
704-
705-
it('throws when suspending on the server', () => {
706-
function AsyncFoo() {
707-
throw new Promise(() => {});
708-
}
709-
710-
expect(() => {
711-
ReactDOMServer.renderToString(<AsyncFoo />);
712-
}).toThrow('ReactDOMServer does not yet support Suspense.');
713-
});
714-
}
715-
716679
it('does not get confused by throwing null', () => {
717680
function Bad() {
718681
// eslint-disable-next-line no-throw-literal

packages/react-dom/src/client/ReactDOMHostConfig.js

+11-14
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';
6363

6464
import {
6565
enableClientRenderFallbackOnHydrationMismatch,
66-
enableSuspenseServerRenderer,
6766
enableCreateEventHandleAPI,
6867
enableScopeAPI,
6968
} from 'shared/ReactFeatureFlags';
@@ -747,19 +746,17 @@ function getNextHydratable(node) {
747746
if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) {
748747
break;
749748
}
750-
if (enableSuspenseServerRenderer) {
751-
if (nodeType === COMMENT_NODE) {
752-
const nodeData = (node: any).data;
753-
if (
754-
nodeData === SUSPENSE_START_DATA ||
755-
nodeData === SUSPENSE_FALLBACK_START_DATA ||
756-
nodeData === SUSPENSE_PENDING_START_DATA
757-
) {
758-
break;
759-
}
760-
if (nodeData === SUSPENSE_END_DATA) {
761-
return null;
762-
}
749+
if (nodeType === COMMENT_NODE) {
750+
const nodeData = (node: any).data;
751+
if (
752+
nodeData === SUSPENSE_START_DATA ||
753+
nodeData === SUSPENSE_FALLBACK_START_DATA ||
754+
nodeData === SUSPENSE_PENDING_START_DATA
755+
) {
756+
break;
757+
}
758+
if (nodeData === SUSPENSE_END_DATA) {
759+
return null;
763760
}
764761
}
765762
}

packages/react-dom/src/server/ReactPartialRenderer.js

+38-47
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
warnAboutDeprecatedLifecycles,
2222
disableLegacyContext,
2323
disableModulePatternComponents,
24-
enableSuspenseServerRenderer,
2524
enableScopeAPI,
2625
} from 'shared/ReactFeatureFlags';
2726
import {
@@ -965,21 +964,17 @@ class ReactDOMServerRenderer {
965964
outBuffer += this.render(child, frame.context, frame.domNamespace);
966965
} catch (err) {
967966
if (err != null && typeof err.then === 'function') {
968-
if (enableSuspenseServerRenderer) {
969-
if (this.suspenseDepth <= 0) {
970-
throw new Error(
971-
// TODO: include component name. This is a bit tricky with current factoring.
972-
'A React component suspended while rendering, but no fallback UI was specified.\n' +
973-
'\n' +
974-
'Add a <Suspense fallback=...> component higher in the tree to ' +
975-
'provide a loading indicator or placeholder to display.',
976-
);
977-
}
978-
979-
suspended = true;
980-
} else {
981-
throw new Error('ReactDOMServer does not yet support Suspense.');
967+
if (this.suspenseDepth <= 0) {
968+
throw new Error(
969+
// TODO: include component name. This is a bit tricky with current factoring.
970+
'A React component suspended while rendering, but no fallback UI was specified.\n' +
971+
'\n' +
972+
'Add a <Suspense fallback=...> component higher in the tree to ' +
973+
'provide a loading indicator or placeholder to display.',
974+
);
982975
}
976+
977+
suspended = true;
983978
} else {
984979
throw err;
985980
}
@@ -1097,39 +1092,35 @@ class ReactDOMServerRenderer {
10971092
return '';
10981093
}
10991094
case REACT_SUSPENSE_TYPE: {
1100-
if (enableSuspenseServerRenderer) {
1101-
const fallback = ((nextChild: any): ReactElement).props.fallback;
1102-
const fallbackChildren = toArray(fallback);
1103-
const nextChildren = toArray(
1104-
((nextChild: any): ReactElement).props.children,
1105-
);
1106-
const fallbackFrame: Frame = {
1107-
type: null,
1108-
domNamespace: parentNamespace,
1109-
children: fallbackChildren,
1110-
childIndex: 0,
1111-
context: context,
1112-
footer: '<!--/$-->',
1113-
};
1114-
const frame: Frame = {
1115-
fallbackFrame,
1116-
type: REACT_SUSPENSE_TYPE,
1117-
domNamespace: parentNamespace,
1118-
children: nextChildren,
1119-
childIndex: 0,
1120-
context: context,
1121-
footer: '<!--/$-->',
1122-
};
1123-
if (__DEV__) {
1124-
((frame: any): FrameDev).debugElementStack = [];
1125-
((fallbackFrame: any): FrameDev).debugElementStack = [];
1126-
}
1127-
this.stack.push(frame);
1128-
this.suspenseDepth++;
1129-
return '<!--$-->';
1130-
} else {
1131-
throw new Error('ReactDOMServer does not yet support Suspense.');
1095+
const fallback = ((nextChild: any): ReactElement).props.fallback;
1096+
const fallbackChildren = toArray(fallback);
1097+
const nextChildren = toArray(
1098+
((nextChild: any): ReactElement).props.children,
1099+
);
1100+
const fallbackFrame: Frame = {
1101+
type: null,
1102+
domNamespace: parentNamespace,
1103+
children: fallbackChildren,
1104+
childIndex: 0,
1105+
context: context,
1106+
footer: '<!--/$-->',
1107+
};
1108+
const frame: Frame = {
1109+
fallbackFrame,
1110+
type: REACT_SUSPENSE_TYPE,
1111+
domNamespace: parentNamespace,
1112+
children: nextChildren,
1113+
childIndex: 0,
1114+
context: context,
1115+
footer: '<!--/$-->',
1116+
};
1117+
if (__DEV__) {
1118+
((frame: any): FrameDev).debugElementStack = [];
1119+
((fallbackFrame: any): FrameDev).debugElementStack = [];
11321120
}
1121+
this.stack.push(frame);
1122+
this.suspenseDepth++;
1123+
return '<!--$-->';
11331124
}
11341125
// eslint-disable-next-line-no-fallthrough
11351126
case REACT_SCOPE_TYPE: {

packages/react-reconciler/src/ReactFiberBeginWork.new.js

+72-79
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ import {
9696
disableModulePatternComponents,
9797
enableProfilerCommitHooks,
9898
enableProfilerTimer,
99-
enableSuspenseServerRenderer,
10099
warnAboutDefaultPropsOnFunctionComponents,
101100
enableScopeAPI,
102101
enableCache,
@@ -2134,17 +2133,15 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
21342133
// If we're currently hydrating, try to hydrate this boundary.
21352134
tryToClaimNextHydratableInstance(workInProgress);
21362135
// This could've been a dehydrated suspense component.
2137-
if (enableSuspenseServerRenderer) {
2138-
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
2139-
if (suspenseState !== null) {
2140-
const dehydrated = suspenseState.dehydrated;
2141-
if (dehydrated !== null) {
2142-
return mountDehydratedSuspenseComponent(
2143-
workInProgress,
2144-
dehydrated,
2145-
renderLanes,
2146-
);
2147-
}
2136+
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
2137+
if (suspenseState !== null) {
2138+
const dehydrated = suspenseState.dehydrated;
2139+
if (dehydrated !== null) {
2140+
return mountDehydratedSuspenseComponent(
2141+
workInProgress,
2142+
dehydrated,
2143+
renderLanes,
2144+
);
21482145
}
21492146
}
21502147

@@ -2220,59 +2217,57 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
22202217
// The current tree is already showing a fallback
22212218

22222219
// Special path for hydration
2223-
if (enableSuspenseServerRenderer) {
2224-
const dehydrated = prevState.dehydrated;
2225-
if (dehydrated !== null) {
2226-
if (!didSuspend) {
2227-
return updateDehydratedSuspenseComponent(
2228-
current,
2229-
workInProgress,
2230-
dehydrated,
2231-
prevState,
2232-
renderLanes,
2233-
);
2234-
} else if (workInProgress.flags & ForceClientRender) {
2235-
// Something errored during hydration. Try again without hydrating.
2236-
workInProgress.flags &= ~ForceClientRender;
2237-
return retrySuspenseComponentWithoutHydrating(
2238-
current,
2239-
workInProgress,
2240-
renderLanes,
2241-
new Error(
2242-
'There was an error while hydrating this Suspense boundary. ' +
2243-
'Switched to client rendering.',
2244-
),
2245-
);
2246-
} else if (
2247-
(workInProgress.memoizedState: null | SuspenseState) !== null
2248-
) {
2249-
// Something suspended and we should still be in dehydrated mode.
2250-
// Leave the existing child in place.
2251-
workInProgress.child = current.child;
2252-
// The dehydrated completion pass expects this flag to be there
2253-
// but the normal suspense pass doesn't.
2254-
workInProgress.flags |= DidCapture;
2255-
return null;
2256-
} else {
2257-
// Suspended but we should no longer be in dehydrated mode.
2258-
// Therefore we now have to render the fallback.
2259-
renderDidSuspendDelayIfPossible();
2260-
const nextPrimaryChildren = nextProps.children;
2261-
const nextFallbackChildren = nextProps.fallback;
2262-
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
2263-
current,
2264-
workInProgress,
2265-
nextPrimaryChildren,
2266-
nextFallbackChildren,
2267-
renderLanes,
2268-
);
2269-
const primaryChildFragment: Fiber = (workInProgress.child: any);
2270-
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
2271-
renderLanes,
2272-
);
2273-
workInProgress.memoizedState = SUSPENDED_MARKER;
2274-
return fallbackChildFragment;
2275-
}
2220+
const dehydrated = prevState.dehydrated;
2221+
if (dehydrated !== null) {
2222+
if (!didSuspend) {
2223+
return updateDehydratedSuspenseComponent(
2224+
current,
2225+
workInProgress,
2226+
dehydrated,
2227+
prevState,
2228+
renderLanes,
2229+
);
2230+
} else if (workInProgress.flags & ForceClientRender) {
2231+
// Something errored during hydration. Try again without hydrating.
2232+
workInProgress.flags &= ~ForceClientRender;
2233+
return retrySuspenseComponentWithoutHydrating(
2234+
current,
2235+
workInProgress,
2236+
renderLanes,
2237+
new Error(
2238+
'There was an error while hydrating this Suspense boundary. ' +
2239+
'Switched to client rendering.',
2240+
),
2241+
);
2242+
} else if (
2243+
(workInProgress.memoizedState: null | SuspenseState) !== null
2244+
) {
2245+
// Something suspended and we should still be in dehydrated mode.
2246+
// Leave the existing child in place.
2247+
workInProgress.child = current.child;
2248+
// The dehydrated completion pass expects this flag to be there
2249+
// but the normal suspense pass doesn't.
2250+
workInProgress.flags |= DidCapture;
2251+
return null;
2252+
} else {
2253+
// Suspended but we should no longer be in dehydrated mode.
2254+
// Therefore we now have to render the fallback.
2255+
renderDidSuspendDelayIfPossible();
2256+
const nextPrimaryChildren = nextProps.children;
2257+
const nextFallbackChildren = nextProps.fallback;
2258+
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
2259+
current,
2260+
workInProgress,
2261+
nextPrimaryChildren,
2262+
nextFallbackChildren,
2263+
renderLanes,
2264+
);
2265+
const primaryChildFragment: Fiber = (workInProgress.child: any);
2266+
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
2267+
renderLanes,
2268+
);
2269+
workInProgress.memoizedState = SUSPENDED_MARKER;
2270+
return fallbackChildFragment;
22762271
}
22772272
}
22782273

@@ -3657,20 +3652,18 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
36573652
case SuspenseComponent: {
36583653
const state: SuspenseState | null = workInProgress.memoizedState;
36593654
if (state !== null) {
3660-
if (enableSuspenseServerRenderer) {
3661-
if (state.dehydrated !== null) {
3662-
pushSuspenseContext(
3663-
workInProgress,
3664-
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3665-
);
3666-
// We know that this component will suspend again because if it has
3667-
// been unsuspended it has committed as a resolved Suspense component.
3668-
// If it needs to be retried, it should have work scheduled on it.
3669-
workInProgress.flags |= DidCapture;
3670-
// We should never render the children of a dehydrated boundary until we
3671-
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
3672-
return null;
3673-
}
3655+
if (state.dehydrated !== null) {
3656+
pushSuspenseContext(
3657+
workInProgress,
3658+
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3659+
);
3660+
// We know that this component will suspend again because if it has
3661+
// been unsuspended it has committed as a resolved Suspense component.
3662+
// If it needs to be retried, it should have work scheduled on it.
3663+
workInProgress.flags |= DidCapture;
3664+
// We should never render the children of a dehydrated boundary until we
3665+
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
3666+
return null;
36743667
}
36753668

36763669
// If this boundary is currently timed out, we need to decide

0 commit comments

Comments
 (0)