Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fizz] add avoidThisFallback support #22318

Merged
merged 21 commits into from
Sep 20, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1478,4 +1478,152 @@ describe('ReactDOMFizzServer', () => {
// We should've been able to display the content without waiting for the rest of the fallback.
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
});

// @gate experimental && enableFizzSuspenseAvoidThisFallback
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>,
);
});
});
17 changes: 17 additions & 0 deletions packages/react-dom/src/server/ReactDOMServerFormatConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,23 @@ const startClientRenderedSuspenseBoundary = stringToPrecomputedChunk(
);
const endSuspenseBoundary = stringToPrecomputedChunk('<!--/$-->');

export function pushStartCompletedSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
) {
target.push(startCompletedSuspenseBoundary);
}

export function pushEndSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
type: string,
props: Object,
responseState: ResponseState,
formatContext: FormatContext,
assignID: null | SuspenseBoundaryID,
salazarm marked this conversation as resolved.
Show resolved Hide resolved
) {
target.push(endSuspenseBoundary);
}

export function writeStartCompletedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ export function writeStartCompletedSuspenseBoundary(
writeChunk(destination, SUSPENSE_COMPLETE);
return writeChunk(destination, formatID(id));
salazarm marked this conversation as resolved.
Show resolved Hide resolved
}

export function pushStartCompletedSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
): void {
target.push(SUSPENSE_COMPLETE);
}

export function writeStartPendingSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
Expand All @@ -231,6 +238,11 @@ export function writeEndCompletedSuspenseBoundary(
): boolean {
return writeChunk(destination, END);
}
export function pushEndCompletedSuspenseBoundary(
salazarm marked this conversation as resolved.
Show resolved Hide resolved
target: Array<Chunk | PrecomputedChunk>,
): void {
target.push(END);
}
export function writeEndPendingSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
Expand Down
29 changes: 28 additions & 1 deletion 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,
pushEndSuspenseBoundary,
createSuspenseBoundaryID,
getChildFormatContext,
} from './ReactServerFormatConfig';
Expand Down Expand Up @@ -107,6 +109,7 @@ import {
warnAboutDefaultPropsOnFunctionComponents,
enableScopeAPI,
enableLazyElements,
enableFizzSuspenseAvoidThisFallback,
} 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);
pushEndSuspenseBoundary(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 (
enableFizzSuspenseAvoidThisFallback &&
props.unstable_avoidThisFallback === true
) {
renderBackupSuspenseBoundary(request, task, props);
} else {
renderSuspenseBoundary(request, task, props);
}
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ 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 pushEndSuspenseBoundary = $$$hostConfig.pushEndSuspenseBoundary;
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 enableFizzSuspenseAvoidThisFallback = false;
salazarm marked this conversation as resolved.
Show resolved Hide resolved

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 enableFizzSuspenseAvoidThisFallback = 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 enableFizzSuspenseAvoidThisFallback = 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 enableFizzSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const enableGetInspectorDataForInstanceInProduction = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableFizzSuspenseAvoidThisFallback = false;
export const enableStrictEffects = false;
export const createRootStrictEffectsByDefault = false;
export const enableUseRefAccessWarning = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const disableModulePatternComponents = true;
export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableFizzSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.testing.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 enableFizzSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.testing.www.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const disableModulePatternComponents = true;
export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableFizzSuspenseAvoidThisFallback = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = !__EXPERIMENTAL__;
export const enableFilterEmptyStringAttributesDOM = false;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.www-dynamic.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const enableLazyContextPropagation = __VARIANT__;
export const enableSyncDefaultUpdates = __VARIANT__;
export const consoleManagedByDevToolsDuringStrictMode = __VARIANT__;
export const warnOnSubscriptionInsideStartTransition = __VARIANT__;
export const enableFizzSuspenseAvoidThisFallback = __VARIANT__;

// Enable this flag to help with concurrent mode debugging.
// It logs information to the console about React scheduling, rendering, and commit phases.
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.www.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const {
enableLazyContextPropagation,
enableSyncDefaultUpdates,
warnOnSubscriptionInsideStartTransition,
enableFizzSuspenseAvoidThisFallback,
} = dynamicFeatureFlags;

// On WWW, __EXPERIMENTAL__ is used for a new modern build.
Expand Down