Repository: facebook/react
Pull request: Assign resolved outlined props to element object (and not only tuple)

When an element is used multiple times as shown in #30526, its props might be deduped. When resolving the reference to the deduped props, we were only updating the React element tuple, which at this point has already been parsed into a React element object, using `null` as placeholder props. Therefore, updating the element tuple doesn't help much, and we need to make sure that the element object's props are updated as well.

This is a similar fix as #28669, see the code lines above, and thus feels similarly hacky. Maybe there's a better way to fix this? @eps1lon was mentioning offline that solving [this TODO](https://github.com/facebook/react/blob/33e54fa252b9dbe7553ef42a2287c3dbbd4f035d/packages/react-client/src/ReactFlightClient.js#L1327) would probably fix it properly, since we wouldn't need to deal with stale tuples then. But that's a way heavier lift.

Changed files:
filename: packages/react-client/src/ReactFlightClient.js
patch: @@ -902,6 +902,20 @@ function waitForReference<T>(
       handler.value = parentObject[key];
     }
 
+    // If the parent object is an unparsed React element tuple and its outlined
+    // props have now been resolved, we also need to update the props of the
+    // parsed element object (i.e. handler.value).
+    if (
+      parentObject[0] === REACT_ELEMENT_TYPE &&
+      key === '3' &&
+      typeof handler.value === 'object' &&
+      handler.value !== null &&
+      handler.value.$$typeof === REACT_ELEMENT_TYPE &&
+      handler.value.props === null
+    ) {
+      handler.value.props = parentObject[key];
+    }
+
     handler.deps--;
 
     if (handler.deps === 0) {

filename: packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
patch: @@ -525,6 +525,104 @@ describe('ReactFlightDOMBrowser', () => {
     expect(container.innerHTML).toBe('{"foo":1}{"foo":1}');
   });
 
+  it('should handle deduped props of re-used elements in fragments (same-chunk reference)', async () => {
+    let resolveFooClientComponentChunk;
+
+    const FooClient = clientExports(
+      function Foo({children, item}) {
+        return children;
+      },
+      '1',
+      '/foo.js',
+      new Promise(resolve => (resolveFooClientComponentChunk = resolve)),
+    );
+
+    const shared = <div />;
+
+    function Server() {
+      return (
+        <FooClient track={shared}>
+          <>{shared}</>
+        </FooClient>
+      );
+    }
+
+    const stream = await serverAct(() =>
+      ReactServerDOMServer.renderToReadableStream(<Server />, 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(() => {
+      resolveFooClientComponentChunk();
+    });
+
+    expect(container.innerHTML).toBe('<div></div>');
+  });
+
+  it('should handle deduped props of re-used elements in server components (cross-chunk reference)', async () => {
+    let resolveFooClientComponentChunk;
+
+    function PassthroughServerComponent({children}) {
+      return children;
+    }
+
+    const FooClient = clientExports(
+      function Foo({children, item}) {
+        return children;
+      },
+      '1',
+      '/foo.js',
+      new Promise(resolve => (resolveFooClientComponentChunk = resolve)),
+    );
+
+    const shared = <div />;
+
+    function Server() {
+      return (
+        <FooClient track={shared}>
+          <PassthroughServerComponent>{shared}</PassthroughServerComponent>
+        </FooClient>
+      );
+    }
+
+    const stream = await serverAct(() =>
+      ReactServerDOMServer.renderToReadableStream(<Server />, 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(() => {
+      resolveFooClientComponentChunk();
+    });
+
+    expect(container.innerHTML).toBe('<div></div>');
+  });
+
   it('should progressively reveal server components', async () => {
     let reportedErrors = [];