diff --git a/fixtures/flight/src/Button.js b/fixtures/flight/src/Button.js index 811fb8da76ffb..bbdaeba2bde9c 100644 --- a/fixtures/flight/src/Button.js +++ b/fixtures/flight/src/Button.js @@ -3,11 +3,21 @@ import * as React from 'react'; export default function Button({action, children}) { + const [isPending, setIsPending] = React.useState(false); + return ( diff --git a/fixtures/flight/src/actions.js b/fixtures/flight/src/actions.js index 6534908e01ed6..f7dccd6a62f65 100644 --- a/fixtures/flight/src/actions.js +++ b/fixtures/flight/src/actions.js @@ -1,6 +1,13 @@ 'use server'; export async function like() { - console.log('Like'); - return 'Liked'; + return new Promise((resolve, reject) => + setTimeout( + () => + Math.random() > 0.5 + ? resolve('Liked') + : reject(new Error('Failed to like')), + 500 + ) + ); } diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index 157301ba58e0b..348aba5ee9ac0 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -850,4 +850,49 @@ describe('ReactFlightDOMBrowser', () => { const result = await actionProxy('!'); expect(result).toBe('Hello World!'); }); + + it('propagates server reference errors to the client', async () => { + let actionProxy; + + function Client({action}) { + actionProxy = action; + return 'Click Me'; + } + + async function send(text) { + return Promise.reject(new Error(`Error for ${text}`)); + } + + const ServerModule = serverExports({send}); + const ClientRef = clientExports(Client); + + const stream = ReactServerDOMWriter.renderToReadableStream( + , + webpackMap, + ); + + const response = ReactServerDOMReader.createFromReadableStream(stream, { + async callServer(ref, args) { + const fn = requireServerRef(ref); + return ReactServerDOMReader.createFromReadableStream( + ReactServerDOMWriter.renderToReadableStream(fn.apply(null, args)), + ); + }, + }); + + function App() { + return use(response); + } + + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render(); + }); + + const expectedError = new Error('Error for test'); + spyOnDevAndProd(console, 'error').mockImplementation(() => {}); + await expect(actionProxy('test')).rejects.toThrow(expectedError); + expect(console.error.mock.calls).toEqual([[expectedError]]); + }); }); diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 20800530b831c..f610f99b49161 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -293,6 +293,8 @@ function serializeThenable(request: Request, thenable: Thenable): number { } else { emitErrorChunkProd(request, newTask.id, digest); } + newTask.status = ERRORED; + pingTask(request, newTask); }, );