From eee695137abc3e362d560593edff520ba390a778 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 4 Apr 2024 22:10:22 -0400 Subject: [PATCH] Support FormData from Server to Client --- .../react-client/src/ReactFlightClient.js | 10 ++++++ .../src/__tests__/ReactFlight-test.js | 34 +++++++++++++++++++ .../react-server/src/ReactFlightServer.js | 14 ++++++++ 3 files changed, 58 insertions(+) diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 082f94261bfd7..dce83642f7fd0 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -736,6 +736,16 @@ function parseModelString( const data = getOutlinedModel(response, id); return new Set(data); } + case 'K': { + // FormData + const id = parseInt(value.slice(2), 16); + const data = getOutlinedModel(response, id); + const formData = new FormData(); + for (let i = 0; i < data.length; i++) { + formData.append(data[i][0], data[i][1]); + } + return formData; + } case 'I': { // $Infinity return Infinity; diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 55956ddc93ecd..ff673c3faff5d 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -468,6 +468,40 @@ describe('ReactFlight', () => { `); }); + if (typeof FormData !== 'undefined') { + it('can transport FormData (no blobs)', async () => { + function ComponentClient({prop}) { + return ` + formData: ${prop instanceof FormData} + hi: ${prop.get('hi')} + multiple: ${prop.getAll('multiple')} + content: ${JSON.stringify(Array.from(prop))} + `; + } + const Component = clientReference(ComponentClient); + + const formData = new FormData(); + formData.append('hi', 'world'); + formData.append('multiple', 1); + formData.append('multiple', 2); + + const model = ; + + const transport = ReactNoopFlightServer.render(model); + + await act(async () => { + ReactNoop.render(await ReactNoopFlightClient.read(transport)); + }); + + expect(ReactNoop).toMatchRenderedOutput(` + formData: true + hi: world + multiple: 1,2 + content: [["hi","world"],["multiple","1"],["multiple","2"]] + `); + }); + } + it('can transport cyclic objects', async () => { function ComponentClient({prop}) { expect(prop.obj.obj.obj).toBe(prop.obj.obj); diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index ad0a28f37175c..2a8e269fee90f 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -1138,6 +1138,12 @@ function serializeMap( return '$Q' + id.toString(16); } +function serializeFormData(request: Request, formData: FormData): string { + const entries = Array.from(formData.entries()); + const id = outlineModel(request, (entries: any)); + return '$K' + id.toString(16); +} + function serializeSet(request: Request, set: Set): string { const entries = Array.from(set); for (let i = 0; i < entries.length; i++) { @@ -1506,6 +1512,10 @@ function renderModelDestructive( if (value instanceof Set) { return serializeSet(request, value); } + // TODO: FormData is not available in old Node. Remove the typeof later. + if (typeof FormData === 'function' && value instanceof FormData) { + return serializeFormData(request, value); + } if (enableBinaryFlight) { if (value instanceof ArrayBuffer) { @@ -2027,6 +2037,10 @@ function renderConsoleValue( if (value instanceof Set) { return serializeSet(request, value); } + // TODO: FormData is not available in old Node. Remove the typeof later. + if (typeof FormData === 'function' && value instanceof FormData) { + return serializeFormData(request, value); + } if (enableBinaryFlight) { if (value instanceof ArrayBuffer) {