diff --git a/fixtures/flight/__tests__/__e2e__/action.test.js b/fixtures/flight/__tests__/__e2e__/action.test.js
new file mode 100644
index 0000000000000..e52623322ccd3
--- /dev/null
+++ b/fixtures/flight/__tests__/__e2e__/action.test.js
@@ -0,0 +1,35 @@
+// @ts-check
+
+import {test, expect} from '@playwright/test';
+
+test('action returning client component with deduped references', async ({
+ page,
+}) => {
+ const pageErrors = [];
+
+ page.on('pageerror', error => {
+ pageErrors.push(error.stack);
+ });
+
+ await page.goto('/');
+
+ const button = await page.getByRole('button', {
+ name: 'Return element from action',
+ });
+
+ await button.click();
+
+ await expect(
+ page.getByTestId('temporary-references-action-result')
+ ).toHaveText('Hello');
+
+ // Click the button one more time to send the previous result (i.e. the
+ // returned element) back to the server.
+ await button.click();
+
+ await expect(pageErrors).toEqual([]);
+
+ await expect(
+ page.getByTestId('temporary-references-action-result')
+ ).toHaveText('HelloHello');
+});
diff --git a/fixtures/flight/server/region.js b/fixtures/flight/server/region.js
index bc4ba05ddf3b4..cb1aac5da8d73 100644
--- a/fixtures/flight/server/region.js
+++ b/fixtures/flight/server/region.js
@@ -50,7 +50,7 @@ const {readFile} = require('fs').promises;
const React = require('react');
-async function renderApp(res, returnValue, formState) {
+async function renderApp(res, returnValue, formState, temporaryReferences) {
const {renderToPipeableStream} = await import(
'react-server-dom-webpack/server'
);
@@ -101,7 +101,9 @@ async function renderApp(res, returnValue, formState) {
);
// For client-invoked server actions we refresh the tree and return a return value.
const payload = {root, returnValue, formState};
- const {pipe} = renderToPipeableStream(payload, moduleMap);
+ const {pipe} = renderToPipeableStream(payload, moduleMap, {
+ temporaryReferences,
+ });
pipe(res);
}
@@ -110,8 +112,13 @@ app.get('/', async function (req, res) {
});
app.post('/', bodyParser.text(), async function (req, res) {
- const {decodeReply, decodeReplyFromBusboy, decodeAction, decodeFormState} =
- await import('react-server-dom-webpack/server');
+ const {
+ decodeReply,
+ decodeReplyFromBusboy,
+ decodeAction,
+ decodeFormState,
+ createTemporaryReferenceSet,
+ } = await import('react-server-dom-webpack/server');
const serverReference = req.get('rsc-action');
if (serverReference) {
// This is the client-side case
@@ -124,15 +131,17 @@ app.post('/', bodyParser.text(), async function (req, res) {
throw new Error('Invalid action');
}
+ const temporaryReferences = createTemporaryReferenceSet();
+
let args;
if (req.is('multipart/form-data')) {
// Use busboy to streamingly parse the reply from form-data.
const bb = busboy({headers: req.headers});
- const reply = decodeReplyFromBusboy(bb);
+ const reply = decodeReplyFromBusboy(bb, {}, {temporaryReferences});
req.pipe(bb);
args = await reply;
} else {
- args = await decodeReply(req.body);
+ args = await decodeReply(req.body, {}, {temporaryReferences});
}
const result = action.apply(null, args);
try {
@@ -142,7 +151,7 @@ app.post('/', bodyParser.text(), async function (req, res) {
// We handle the error on the client
}
// Refresh the client and return the value
- renderApp(res, result, null);
+ renderApp(res, result, null, temporaryReferences);
} else {
// This is the progressive enhancement case
const UndiciRequest = require('undici').Request;
diff --git a/fixtures/flight/src/App.js b/fixtures/flight/src/App.js
index 027056c515021..1c9575dcfb43e 100644
--- a/fixtures/flight/src/App.js
+++ b/fixtures/flight/src/App.js
@@ -12,10 +12,11 @@ import Button from './Button.js';
import Form from './Form.js';
import {Dynamic} from './Dynamic.js';
import {Client} from './Client.js';
+import {TemporaryReferences} from './TemporaryReferences.js';
import {Note} from './cjs/Note.js';
-import {like, greet, increment} from './actions.js';
+import {like, greet, increment, returnElement} from './actions.js';
import {getServerState} from './ServerState.js';
@@ -61,6 +62,7 @@ export default async function App() {
+