Repository: facebook/react
Pull request: [Flight] don't overwrite existing chunk listeners in 'wakeChunkIfInitialized'

Follow-up to https://github.com/facebook/react/pull/29201. If a chunk had listeners attached already (e.g. because `.then` was called on the chunk returned from `createFromReadableStream`), `wakeChunkIfInitialized` would overwrite any listeners added during chunk initialization. This caused cyclic [path references](https://github.com/facebook/react/pull/28996) within that chunk to never resolve. Fixed by merging the two arrays of listeners.

Changed files:
filename: packages/react-client/src/ReactFlightClient.js
patch: @@ -320,8 +320,24 @@ function wakeChunkIfInitialized<T>(
     case PENDING:
     case BLOCKED:
     case CYCLIC:
-      chunk.value = resolveListeners;
-      chunk.reason = rejectListeners;
+      if (chunk.value) {
+        for (let i = 0; i < resolveListeners.length; i++) {
+          chunk.value.push(resolveListeners[i]);
+        }
+      } else {
+        chunk.value = resolveListeners;
+      }
+
+      if (chunk.reason) {
+        if (rejectListeners) {
+          for (let i = 0; i < rejectListeners.length; i++) {
+            chunk.reason.push(rejectListeners[i]);
+          }
+        }
+      } else {
+        chunk.reason = rejectListeners;
+      }
+
       break;
     case ERRORED:
       if (rejectListeners) {

filename: packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
patch: @@ -296,6 +296,58 @@ describe('ReactFlightDOMBrowser', () => {
     expect(container.innerHTML).toBe('<pre>[[1,2,3],[1,2,3]]</pre>');
   });
 
+  it('should resolve deduped objects within the same model root when it is blocked and there is a listener attached to the root', async () => {
+    let resolveClientComponentChunk;
+
+    const ClientOuter = clientExports(function ClientOuter({Component, value}) {
+      return <Component value={value} />;
+    });
+
+    const ClientInner = clientExports(
+      function ClientInner({value}) {
+        return <pre>{JSON.stringify(value)}</pre>;
+      },
+      '42',
+      '/test.js',
+      new Promise(resolve => (resolveClientComponentChunk = resolve)),
+    );
+
+    function Server({value}) {
+      return <ClientOuter Component={ClientInner} value={value} />;
+    }
+
+    const shared = [1, 2, 3];
+    const value = [shared, shared];
+
+    const stream = ReactServerDOMServer.renderToReadableStream(
+      <Server value={value} />,
+      webpackMap,
+    );
+
+    function ClientRoot({response}) {
+      return use(response);
+    }
+
+    const response = ReactServerDOMClient.createFromReadableStream(stream);
+    // make sure we have a listener so that `resolveModelChunk` initializes the chunk eagerly
+    response.then(() => {});
+
+    const container = document.createElement('div');
+    const root = ReactDOMClient.createRoot(container);
+
+    await act(() => {
+      root.render(<ClientRoot response={response} />);
+    });
+
+    expect(container.innerHTML).toBe('');
+
+    await act(() => {
+      resolveClientComponentChunk();
+    });
+
+    expect(container.innerHTML).toBe('<pre>[[1,2,3],[1,2,3]]</pre>');
+  });
+
   it('should progressively reveal server components', async () => {
     let reportedErrors = [];