Repository: facebook/react
Pull request: [Flight / Flight Reply] Don't clear pending listeners when entering blocked state

Fixes #29200

The cyclic state might have added listeners that will still need to be invoked. This happens if we have a cyclic reference AND end up blocked.

We have already cleared these before entering the parsing when we enter the CYCLIC state so we they already have the right type. If listeners are added during this phase they should carry over to the blocked state.


Changed files:
filename: packages/react-client/src/ReactFlightClient.js
patch: @@ -506,8 +506,6 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
       // We have to go the BLOCKED state until they're resolved.
       const blockedChunk: BlockedChunk<T> = (chunk: any);
       blockedChunk.status = BLOCKED;
-      blockedChunk.value = null;
-      blockedChunk.reason = null;
     } else {
       const resolveListeners = cyclicChunk.value;
       const initializedChunk: InitializedChunk<T> = (chunk: any);

filename: packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
patch: @@ -247,6 +247,55 @@ describe('ReactFlightDOMBrowser', () => {
     expect(container.innerHTML).toBe('<span>Hello, World!</span>');
   });
 
+  it('should resolve deduped objects within the same model root when it is blocked', 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);
+    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 = [];
 

filename: packages/react-server/src/ReactFlightReplyServer.js
patch: @@ -464,8 +464,6 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
       // We have to go the BLOCKED state until they're resolved.
       const blockedChunk: BlockedChunk<T> = (chunk: any);
       blockedChunk.status = BLOCKED;
-      blockedChunk.value = null;
-      blockedChunk.reason = null;
     } else {
       const resolveListeners = cyclicChunk.value;
       const initializedChunk: InitializedChunk<T> = (chunk: any);