diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js
index e82171d24de5..97bee6c04c60 100644
--- a/packages/react-server/src/ReactFlightServer.js
+++ b/packages/react-server/src/ReactFlightServer.js
@@ -460,7 +460,7 @@ export function resolveModelToJSON(
const newSegment = createSegment(request, () => value);
const ping = newSegment.ping;
x.then(ping, ping);
- return serializeByValueID(newSegment.id);
+ return serializeByRefID(newSegment.id);
} else {
// Something errored. Don't bother encoding anything up to here.
throw x;
@@ -708,6 +708,7 @@ function flushCompletedChunks(request: Request): void {
break;
}
}
+ moduleChunks.splice(0, i);
// Next comes model data.
const jsonChunks = request.completedJSONChunks;
i = 0;
diff --git a/packages/react-transport-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-transport-dom-webpack/src/__tests__/ReactFlightDOM-test.js
index ebabf4e75f5f..8b690219ec6e 100644
--- a/packages/react-transport-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+++ b/packages/react-transport-dom-webpack/src/__tests__/ReactFlightDOM-test.js
@@ -64,28 +64,34 @@ describe('ReactFlightDOM', () => {
};
}
- function block(render, load) {
+ function moduleReference(moduleExport) {
const idx = webpackModuleIdx++;
webpackModules[idx] = {
- d: render,
+ d: moduleExport,
};
webpackMap['path/' + idx] = {
id: '' + idx,
chunks: [],
name: 'd',
};
+ const MODULE_TAG = Symbol.for('react.module.reference');
+ return {$$typeof: MODULE_TAG, name: 'path/' + idx};
+ }
+
+ function block(render, load) {
if (load === undefined) {
return () => {
- return ReactTransportDOMServerRuntime.serverBlockNoData('path/' + idx);
+ return ReactTransportDOMServerRuntime.serverBlockNoData(
+ moduleReference(render),
+ );
};
}
return function(...args) {
const curriedLoad = () => {
return load(...args);
};
- const MODULE_TAG = Symbol.for('react.module.reference');
return ReactTransportDOMServerRuntime.serverBlock(
- {$$typeof: MODULE_TAG, name: 'path/' + idx},
+ moduleReference(render),
curriedLoad,
);
};
@@ -314,6 +320,9 @@ describe('ReactFlightDOM', () => {
return 'data';
}
function DelayedText({children}, data) {
+ if (data !== 'data') {
+ throw new Error('No data');
+ }
return
Game over
', // TODO: should not have message in prod. ); }); + + // @gate experimental + it('should progressively reveal server components', async () => { + const {Suspense} = React; + + // Client Components + + class ErrorBoundary extends React.Component { + state = {hasError: false, error: null}; + static getDerivedStateFromError(error) { + return { + hasError: true, + error, + }; + } + render() { + if (this.state.hasError) { + return this.props.fallback(this.state.error); + } + return this.props.children; + } + } + + function MyErrorBoundary({children}) { + return ( +{e.message}
}> + {children} +(loading)
'); + + // This isn't enough to show anything. + await act(async () => { + resolveFriends(); + }); + expect(container.innerHTML).toBe('(loading)
'); + + // We can now show the details. Sidebar and posts are still loading. + await act(async () => { + resolveName(); + }); + // Advance time enough to trigger a nested fallback. + jest.advanceTimersByTime(500); + expect(container.innerHTML).toBe( + '(loading sidebar)
' + + '(loading posts)
' + + '(loading games)
', + ); + + // Let's *fail* loading games. + await act(async () => { + rejectGames(new Error('Game over')); + }); + expect(container.innerHTML).toBe( + '(loading sidebar)
' + + '(loading posts)
' + + 'Game over
', // TODO: should not have message in prod. + ); + + // We can now show the sidebar. + await act(async () => { + resolvePhotos(); + }); + expect(container.innerHTML).toBe( + '(loading posts)
' + + 'Game over
', // TODO: should not have message in prod. + ); + + // Show everything. + await act(async () => { + resolvePosts(); + }); + expect(container.innerHTML).toBe( + 'Game over
', // TODO: should not have message in prod. + ); + }); });