Skip to content

Commit

Permalink
[Fizz] add avoidThisFallback support (#22318)
Browse files Browse the repository at this point in the history
  • Loading branch information
salazarm committed Sep 20, 2021
1 parent cbf6178 commit 64e70f8
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 24 deletions.
148 changes: 148 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
14 changes: 12 additions & 2 deletions packages/react-dom/src/server/ReactDOMServerFormatConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -1497,7 +1508,6 @@ export function writeStartPendingSuspenseBoundary(
export function writeStartClientRenderedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
id: SuspenseBoundaryID,
): boolean {
return writeChunk(destination, startClientRenderedSuspenseBoundary);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export {
pushEmpty,
pushStartInstance,
pushEndInstance,
pushStartCompletedSuspenseBoundary,
pushEndCompletedSuspenseBoundary,
writeStartSegment,
writeEndSegment,
writeCompletedSegmentInstruction,
Expand Down Expand Up @@ -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
Expand All @@ -142,7 +138,6 @@ export function writeStartClientRenderedSuspenseBoundary(
return writeStartClientRenderedSuspenseBoundaryImpl(
destination,
responseState,
id,
);
}
export function writeEndCompletedSuspenseBoundary(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -220,17 +225,20 @@ 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,
responseState: ResponseState,
): boolean {
return writeChunk(destination, END);
}
export function pushEndCompletedSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
): void {
target.push(END);
}
export function writeEndPendingSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
Expand Down
37 changes: 29 additions & 8 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ import {
pushTextInstance,
pushStartInstance,
pushEndInstance,
pushStartCompletedSuspenseBoundary,
pushEndCompletedSuspenseBoundary,
createSuspenseBoundaryID,
getChildFormatContext,
} from './ReactServerFormatConfig';
Expand Down Expand Up @@ -107,6 +109,7 @@ import {
warnAboutDefaultPropsOnFunctionComponents,
enableScopeAPI,
enableLazyElements,
enableSuspenseAvoidThisFallback,
} from 'shared/ReactFeatureFlags';

import getComponentNameFromType from 'shared/getComponentNameFromType';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -1604,7 +1631,6 @@ function flushSegment(
writeStartClientRenderedSuspenseBoundary(
destination,
request.responseState,
boundary.id,
);

// Flush the fallback.
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/ReactFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ export const warnAboutSpreadingKeyToJSX = false;

export const warnOnSubscriptionInsideStartTransition = false;

export const enableSuspenseAvoidThisFallback = false;

export const enableComponentStackLocations = true;

export const enableNewReconciler = false;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-oss.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.test-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 64e70f8

Please sign in to comment.