diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
index eebca4710e954..c12c6904b872f 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
@@ -32,6 +32,7 @@ let ReactDOMClient;
let ReactServerDOMServer;
let ReactServerDOMClient;
let ReactDOMFizzServer;
+let ReactDOMStaticServer;
let Suspense;
let ErrorBoundary;
let JSDOM;
@@ -71,6 +72,7 @@ describe('ReactFlightDOM', () => {
Suspense = React.Suspense;
ReactDOMClient = require('react-dom/client');
ReactDOMFizzServer = require('react-dom/server.node');
+ ReactDOMStaticServer = require('react-dom/static.node');
ReactServerDOMClient = require('react-server-dom-webpack/client');
ErrorBoundary = class extends React.Component {
@@ -1300,6 +1302,91 @@ describe('ReactFlightDOM', () => {
expect(getMeaningfulChildren(container)).toEqual(
hello world
);
});
+ // @gate enablePostpone
+ it('should allow postponing in Flight through a serialized promise', async () => {
+ const Context = React.createContext();
+ const ContextProvider = Context.Provider;
+
+ function Foo() {
+ const value = React.use(React.useContext(Context));
+ return {value};
+ }
+
+ const ClientModule = clientExports({
+ ContextProvider,
+ Foo,
+ });
+
+ async function getFoo() {
+ React.unstable_postpone('foo');
+ }
+
+ function App() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+
+ const {writable, readable} = getTestStream();
+
+ const {pipe} = ReactServerDOMServer.renderToPipeableStream(
+ ,
+ webpackMap,
+ );
+ pipe(writable);
+
+ let response = null;
+ function getResponse() {
+ if (response === null) {
+ response = ReactServerDOMClient.createFromReadableStream(readable);
+ }
+ return response;
+ }
+
+ function Response() {
+ return getResponse();
+ }
+
+ const errors = [];
+ function onError(error, errorInfo) {
+ errors.push(error, errorInfo);
+ }
+ const result = await ReactDOMStaticServer.prerenderToNodeStream(
+ ,
+ {
+ onError,
+ },
+ );
+
+ const prelude = await new Promise((resolve, reject) => {
+ let content = '';
+ result.prelude.on('data', chunk => {
+ content += Buffer.from(chunk).toString('utf8');
+ });
+ result.prelude.on('error', error => {
+ reject(error);
+ });
+ result.prelude.on('end', () => resolve(content));
+ });
+
+ expect(errors).toEqual([]);
+ const doc = new JSDOM(prelude).window.document;
+ expect(getMeaningfulChildren(doc)).toEqual(
+
+
+ loading...
+
+ ,
+ );
+ });
+
it('should support float methods when rendering in Fizz', async () => {
function Component() {
return hello world
;
diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js
index 479a4a746d5e2..cc65f746f42e8 100644
--- a/packages/react-server/src/ReactFlightServer.js
+++ b/packages/react-server/src/ReactFlightServer.js
@@ -407,11 +407,21 @@ function serializeThenable(request: Request, thenable: Thenable): number {
pingTask(request, newTask);
},
reason => {
- newTask.status = ERRORED;
+ if (
+ enablePostpone &&
+ typeof reason === 'object' &&
+ reason !== null &&
+ (reason: any).$$typeof === REACT_POSTPONE_TYPE
+ ) {
+ const postponeInstance: Postpone = (reason: any);
+ logPostpone(request, postponeInstance.message);
+ emitPostponeChunk(request, newTask.id, postponeInstance);
+ } else {
+ newTask.status = ERRORED;
+ const digest = logRecoverableError(request, reason);
+ emitErrorChunk(request, newTask.id, digest, reason);
+ }
request.abortableTasks.delete(newTask);
- // TODO: We should ideally do this inside performWork so it's scheduled
- const digest = logRecoverableError(request, reason);
- emitErrorChunk(request, newTask.id, digest, reason);
if (request.destination !== null) {
flushCompletedChunks(request, request.destination);
}