diff --git a/fixtures/flight/src/Form.js b/fixtures/flight/src/Form.js
index 8c2ff2922d..a4b92366d4 100644
--- a/fixtures/flight/src/Form.js
+++ b/fixtures/flight/src/Form.js
@@ -20,8 +20,14 @@ export default function Form({action, children}) {
React.startTransition(() => setIsPending(false));
}
}}>
-
+
+
+ {isPending ? 'Saving...' : null}
);
diff --git a/fixtures/flight/src/actions.js b/fixtures/flight/src/actions.js
index 7143c31a39..87cba005e0 100644
--- a/fixtures/flight/src/actions.js
+++ b/fixtures/flight/src/actions.js
@@ -5,5 +5,12 @@ export async function like() {
}
export async function greet(formData) {
- return 'Hi ' + formData.get('name') + '!';
+ const name = formData.get('name') || 'you';
+ const file = formData.get('file');
+ if (file) {
+ return `Ok, ${name}, here is ${file.name}:
+ ${(await file.text()).toUpperCase()}
+ `;
+ }
+ return 'Hi ' + name + '!';
}
diff --git a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js
index b1cda7e104..f23959b2f8 100644
--- a/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js
+++ b/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js
@@ -88,8 +88,17 @@ function decodeReplyFromBusboy(
webpackMap: ServerManifest,
): Thenable {
const response = createResponse(webpackMap, '');
+ let pendingFiles = 0;
+ const queuedFields: Array = [];
busboyStream.on('field', (name, value) => {
- resolveField(response, name, value);
+ if (pendingFiles > 0) {
+ // Because the 'end' event fires two microtasks after the next 'field'
+ // we would resolve files and fields out of order. To handle this properly
+ // we queue any fields we receive until the previous file is done.
+ queuedFields.push(name, value);
+ } else {
+ resolveField(response, name, value);
+ }
});
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
if (encoding.toLowerCase() === 'base64') {
@@ -99,12 +108,21 @@ function decodeReplyFromBusboy(
'the wrong assumption, we can easily fix it.',
);
}
+ pendingFiles++;
const file = resolveFileInfo(response, name, filename, mimeType);
value.on('data', chunk => {
resolveFileChunk(response, file, chunk);
});
value.on('end', () => {
resolveFileComplete(response, name, file);
+ pendingFiles--;
+ if (pendingFiles === 0) {
+ // Release any queued fields
+ for (let i = 0; i < queuedFields.length; i += 2) {
+ resolveField(response, queuedFields[i], queuedFields[i + 1]);
+ }
+ queuedFields.length = 0;
+ }
});
});
busboyStream.on('finish', () => {
diff --git a/packages/react-server/src/ReactFlightReplyServer.js b/packages/react-server/src/ReactFlightReplyServer.js
index 88b846f283..078f76f11f 100644
--- a/packages/react-server/src/ReactFlightReplyServer.js
+++ b/packages/react-server/src/ReactFlightReplyServer.js
@@ -564,8 +564,11 @@ export function resolveFileComplete(
handle: FileHandle,
): void {
// Add this file to the backing store.
- const file = new File(handle.chunks, handle.filename, {type: handle.mime});
- response._formData.append(key, file);
+ // Node.js doesn't expose a global File constructor so we need to use
+ // the append() form that takes the file name as the third argument,
+ // to create a File object.
+ const blob = new Blob(handle.chunks, {type: handle.mime});
+ response._formData.append(key, blob, handle.filename);
}
export function close(response: Response): void {