[Fizz] add avoidThisFallback support (#22318)

This commit is contained in:
salazarm
2021-09-20 15:44:48 -04:00
committed by GitHub
parent cbf6178ed3
commit 64e70f82e9
15 changed files with 220 additions and 24 deletions

View File

@@ -1484,6 +1484,154 @@ describe('ReactDOMFizzServer', () => {
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
});
// @gate experimental && enableSuspenseAvoidThisFallback
it('should respect unstable_avoidThisFallback', async () => {
const resolved = {
0: false,
1: false,
};
const promiseRes = {};
const promises = {
0: new Promise(res => {
promiseRes[0] = () => {
resolved[0] = true;
res();
};
}),
1: new Promise(res => {
promiseRes[1] = () => {
resolved[1] = true;
res();
};
}),
};
const InnerComponent = ({isClient, depth}) => {
if (isClient) {
// Resuspend after re-rendering on client to check that fallback shows on client
throw new Promise(() => {});
}
if (!resolved[depth]) {
throw promises[depth];
}
return (
<div>
<Text text={`resolved ${depth}`} />
</div>
);
};
function App({isClient}) {
return (
<div>
<Text text="Non Suspense Content" />
<Suspense
fallback={
<span>
<Text text="Avoided Fallback" />
</span>
}
unstable_avoidThisFallback={true}>
<InnerComponent isClient={isClient} depth={0} />
<div>
<Suspense fallback={<Text text="Fallback" />}>
<Suspense
fallback={
<span>
<Text text="Avoided Fallback2" />
</span>
}
unstable_avoidThisFallback={true}>
<InnerComponent isClient={isClient} depth={1} />
</Suspense>
</Suspense>
</div>
</Suspense>
</div>
);
}
await jest.runAllTimers();
await act(async () => {
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
<App isClient={false} />,
writable,
);
startWriting();
});
// Nothing is output since root has a suspense with avoidedThisFallback that hasn't resolved
expect(getVisibleChildren(container)).toEqual(undefined);
expect(container.innerHTML).not.toContain('Avoided Fallback');
// resolve first suspense component with avoidThisFallback
await act(async () => {
promiseRes[0]();
});
expect(getVisibleChildren(container)).toEqual(
<div>
Non Suspense Content
<div>resolved 0</div>
<div>Fallback</div>
</div>,
);
expect(container.innerHTML).not.toContain('Avoided Fallback2');
await act(async () => {
promiseRes[1]();
});
expect(getVisibleChildren(container)).toEqual(
<div>
Non Suspense Content
<div>resolved 0</div>
<div>
<div>resolved 1</div>
</div>
</div>,
);
let root;
await act(async () => {
root = ReactDOM.hydrateRoot(container, <App isClient={false} />);
Scheduler.unstable_flushAll();
await jest.runAllTimers();
});
// No change after hydration
expect(getVisibleChildren(container)).toEqual(
<div>
Non Suspense Content
<div>resolved 0</div>
<div>
<div>resolved 1</div>
</div>
</div>,
);
await act(async () => {
// Trigger update by changing isClient to true
root.render(<App isClient={true} />);
Scheduler.unstable_flushAll();
await jest.runAllTimers();
});
// Now that we've resuspended at the root we show the root fallback
expect(getVisibleChildren(container)).toEqual(
<div>
Non Suspense Content
<div style="display: none;">resolved 0</div>
<div style="display: none;">
<div>resolved 1</div>
</div>
<span>Avoided Fallback</span>
</div>,
);
});
// @gate supportsNativeUseSyncExternalStore
// @gate experimental
it('calls getServerSnapshot instead of getSnapshot', async () => {

View File

@@ -1480,10 +1480,21 @@ const startClientRenderedSuspenseBoundary = stringToPrecomputedChunk(
);
const endSuspenseBoundary = stringToPrecomputedChunk('<!--/$-->');
export function pushStartCompletedSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
) {
target.push(startCompletedSuspenseBoundary);
}
export function pushEndCompletedSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
) {
target.push(endSuspenseBoundary);
}
export function writeStartCompletedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
id: SuspenseBoundaryID,
): boolean {
return writeChunk(destination, startCompletedSuspenseBoundary);
}
@@ -1497,7 +1508,6 @@ export function writeStartPendingSuspenseBoundary(
export function writeStartClientRenderedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
id: SuspenseBoundaryID,
): boolean {
return writeChunk(destination, startClientRenderedSuspenseBoundary);
}

View File

@@ -86,6 +86,8 @@ export {
pushEmpty,
pushStartInstance,
pushEndInstance,
pushStartCompletedSuspenseBoundary,
pushEndCompletedSuspenseBoundary,
writeStartSegment,
writeEndSegment,
writeCompletedSegmentInstruction,
@@ -116,23 +118,17 @@ export function pushTextInstance(
export function writeStartCompletedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
id: SuspenseBoundaryID,
): boolean {
if (responseState.generateStaticMarkup) {
// A completed boundary is done and doesn't need a representation in the HTML
// if we're not going to be hydrating it.
return true;
}
return writeStartCompletedSuspenseBoundaryImpl(
destination,
responseState,
id,
);
return writeStartCompletedSuspenseBoundaryImpl(destination, responseState);
}
export function writeStartClientRenderedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
id: SuspenseBoundaryID,
): boolean {
if (responseState.generateStaticMarkup) {
// A client rendered boundary is done and doesn't need a representation in the HTML
@@ -142,7 +138,6 @@ export function writeStartClientRenderedSuspenseBoundary(
return writeStartClientRenderedSuspenseBoundaryImpl(
destination,
responseState,
id,
);
}
export function writeEndCompletedSuspenseBoundary(

View File

@@ -204,11 +204,16 @@ export function writePlaceholder(
export function writeStartCompletedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
id: SuspenseBoundaryID,
): boolean {
writeChunk(destination, SUSPENSE_COMPLETE);
return writeChunk(destination, formatID(id));
return writeChunk(destination, SUSPENSE_COMPLETE);
}
export function pushStartCompletedSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
): void {
target.push(SUSPENSE_COMPLETE);
}
export function writeStartPendingSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
@@ -220,10 +225,8 @@ export function writeStartPendingSuspenseBoundary(
export function writeStartClientRenderedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
id: SuspenseBoundaryID,
): boolean {
writeChunk(destination, SUSPENSE_CLIENT_RENDER);
return writeChunk(destination, formatID(id));
return writeChunk(destination, SUSPENSE_CLIENT_RENDER);
}
export function writeEndCompletedSuspenseBoundary(
destination: Destination,
@@ -231,6 +234,11 @@ export function writeEndCompletedSuspenseBoundary(
): boolean {
return writeChunk(destination, END);
}
export function pushEndCompletedSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
): void {
target.push(END);
}
export function writeEndPendingSuspenseBoundary(
destination: Destination,
responseState: ResponseState,

View File

@@ -52,6 +52,8 @@ import {
pushTextInstance,
pushStartInstance,
pushEndInstance,
pushStartCompletedSuspenseBoundary,
pushEndCompletedSuspenseBoundary,
createSuspenseBoundaryID,
getChildFormatContext,
} from './ReactServerFormatConfig';
@@ -107,6 +109,7 @@ import {
warnAboutDefaultPropsOnFunctionComponents,
enableScopeAPI,
enableLazyElements,
enableSuspenseAvoidThisFallback,
} from 'shared/ReactFeatureFlags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
@@ -520,6 +523,23 @@ function renderSuspenseBoundary(
popComponentStackInDEV(task);
}
function renderBackupSuspenseBoundary(
request: Request,
task: Task,
props: Object,
) {
pushBuiltInComponentStackInDEV(task, 'Suspense');
const content = props.children;
const segment = task.blockedSegment;
pushStartCompletedSuspenseBoundary(segment.chunks);
renderNode(request, task, content);
pushEndCompletedSuspenseBoundary(segment.chunks);
popComponentStackInDEV(task);
}
function renderHostElement(
request: Request,
task: Task,
@@ -986,7 +1006,14 @@ function renderElement(
}
// eslint-disable-next-line-no-fallthrough
case REACT_SUSPENSE_TYPE: {
renderSuspenseBoundary(request, task, props);
if (
enableSuspenseAvoidThisFallback &&
props.unstable_avoidThisFallback === true
) {
renderBackupSuspenseBoundary(request, task, props);
} else {
renderSuspenseBoundary(request, task, props);
}
return;
}
}
@@ -1604,7 +1631,6 @@ function flushSegment(
writeStartClientRenderedSuspenseBoundary(
destination,
request.responseState,
boundary.id,
);
// Flush the fallback.
@@ -1658,12 +1684,7 @@ function flushSegment(
return writeEndPendingSuspenseBoundary(destination, request.responseState);
} else {
// We can inline this boundary's content as a complete boundary.
writeStartCompletedSuspenseBoundary(
destination,
request.responseState,
boundary.id,
);
writeStartCompletedSuspenseBoundary(destination, request.responseState);
const completedSegments = boundary.completedSegments;
invariant(

View File

@@ -39,6 +39,10 @@ export const pushEmpty = $$$hostConfig.pushEmpty;
export const pushTextInstance = $$$hostConfig.pushTextInstance;
export const pushStartInstance = $$$hostConfig.pushStartInstance;
export const pushEndInstance = $$$hostConfig.pushEndInstance;
export const pushStartCompletedSuspenseBoundary =
$$$hostConfig.pushStartCompletedSuspenseBoundary;
export const pushEndCompletedSuspenseBoundary =
$$$hostConfig.pushEndCompletedSuspenseBoundary;
export const writePlaceholder = $$$hostConfig.writePlaceholder;
export const writeStartCompletedSuspenseBoundary =
$$$hostConfig.writeStartCompletedSuspenseBoundary;

View File

@@ -101,6 +101,8 @@ export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = true;
export const enableNewReconciler = false;

View File

@@ -49,6 +49,7 @@ export const disableModulePatternComponents = false;
export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = false;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;

View File

@@ -40,6 +40,7 @@ export const disableModulePatternComponents = false;
export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = false;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;

View File

@@ -40,6 +40,7 @@ export const disableModulePatternComponents = false;
export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;

View File

@@ -50,6 +50,7 @@ export const enableGetInspectorDataForInstanceInProduction = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableStrictEffects = false;
export const createRootStrictEffectsByDefault = false;
export const enableUseRefAccessWarning = false;

View File

@@ -40,6 +40,7 @@ export const disableModulePatternComponents = true;
export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;

View File

@@ -40,6 +40,7 @@ export const disableModulePatternComponents = false;
export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;

View File

@@ -40,6 +40,7 @@ export const disableModulePatternComponents = true;
export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = !__EXPERIMENTAL__;
export const enableFilterEmptyStringAttributesDOM = false;

View File

@@ -48,6 +48,7 @@ export const enableProfilerNestedUpdateScheduledHook =
export const enableUpdaterTracking = __PROFILE__;
export const enableSuspenseLayoutEffectSemantics = true;
export const enableSuspenseAvoidThisFallback = false;
// Logs additional User Timing API marks for use with an experimental profiling tool.
export const enableSchedulingProfiler =