Skip to content

Commit 9e1d958

Browse files
committed
[Flight] Send server reference error chunks to the client
Previously when a called server reference function was rejected, no error chunk was emitted, and the request was not properly closed.
1 parent b72ed69 commit 9e1d958

File tree

4 files changed

+68
-4
lines changed

4 files changed

+68
-4
lines changed

fixtures/flight/src/Button.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,21 @@
33
import * as React from 'react';
44

55
export default function Button({action, children}) {
6+
const [isPending, setIsPending] = React.useState(false);
7+
68
return (
79
<button
10+
disabled={isPending}
811
onClick={async () => {
9-
const result = await action();
10-
console.log(result);
12+
setIsPending(true);
13+
try {
14+
const result = await action();
15+
console.log(result);
16+
} catch (error) {
17+
console.error(error);
18+
} finally {
19+
setIsPending(false);
20+
}
1121
}}>
1222
{children}
1323
</button>

fixtures/flight/src/actions.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
'use server';
22

33
export async function like() {
4-
console.log('Like');
5-
return 'Liked';
4+
return new Promise((resolve, reject) =>
5+
setTimeout(
6+
() =>
7+
Math.random() > 0.5
8+
? resolve('Liked')
9+
: reject(new Error('Failed to like')),
10+
500
11+
)
12+
);
613
}

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,4 +850,49 @@ describe('ReactFlightDOMBrowser', () => {
850850
const result = await actionProxy('!');
851851
expect(result).toBe('Hello World!');
852852
});
853+
854+
it('propagates server reference errors to the client', async () => {
855+
let actionProxy;
856+
857+
function Client({action}) {
858+
actionProxy = action;
859+
return 'Click Me';
860+
}
861+
862+
async function send(text) {
863+
return Promise.reject(new Error(`Error for ${text}`));
864+
}
865+
866+
const ServerModule = serverExports({send});
867+
const ClientRef = clientExports(Client);
868+
869+
const stream = ReactServerDOMWriter.renderToReadableStream(
870+
<ClientRef action={ServerModule.send} />,
871+
webpackMap,
872+
);
873+
874+
const response = ReactServerDOMReader.createFromReadableStream(stream, {
875+
async callServer(ref, args) {
876+
const fn = requireServerRef(ref);
877+
return ReactServerDOMReader.createFromReadableStream(
878+
ReactServerDOMWriter.renderToReadableStream(fn.apply(null, args)),
879+
);
880+
},
881+
});
882+
883+
function App() {
884+
return use(response);
885+
}
886+
887+
const container = document.createElement('div');
888+
const root = ReactDOMClient.createRoot(container);
889+
await act(async () => {
890+
root.render(<App />);
891+
});
892+
893+
const expectedError = new Error('Error for test');
894+
spyOnDevAndProd(console, 'error').mockImplementation(() => {});
895+
await expect(actionProxy('test')).rejects.toThrow(expectedError);
896+
expect(console.error.mock.calls).toEqual([[expectedError]]);
897+
});
853898
});

packages/react-server/src/ReactFlightServer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ function serializeThenable(request: Request, thenable: Thenable<any>): number {
293293
} else {
294294
emitErrorChunkProd(request, newTask.id, digest);
295295
}
296+
newTask.status = ERRORED;
297+
pingTask(request, newTask);
296298
},
297299
);
298300

0 commit comments

Comments
 (0)