Assign resolved outlined props to element object (and not only tuple) (#30528)
Co-authored-by: eps1lon <sebastian.silbermann@vercel.com>
Hendrik Liebau committed
Jul 30, 2024 at 21:31 UTC
3208e73e82ba2bcebed3828fe394fc24d9000903
2 files changed
+112
packages/react-client/src/ReactFlightClient.js
+14
@@ -902,6 +902,20 @@ function waitForReference<T>(
902
handler.value = parentObject[key];
903
}
904
905
+ // If the parent object is an unparsed React element tuple and its outlined
906
+ // props have now been resolved, we also need to update the props of the
907
+ // parsed element object (i.e. handler.value).
908
+ if (
909
+ parentObject[0] === REACT_ELEMENT_TYPE &&
910
+ key === '3' &&
911
+ typeof handler.value === 'object' &&
912
+ handler.value !== null &&
913
+ handler.value.$$typeof === REACT_ELEMENT_TYPE &&
914
+ handler.value.props === null
915
+ ) {
916
+ handler.value.props = parentObject[key];
917
+ }
918
+
919
handler.deps--;
920
921
if (handler.deps === 0) {
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+98
@@ -525,6 +525,104 @@ describe('ReactFlightDOMBrowser', () => {
525
expect(container.innerHTML).toBe('{"foo":1}{"foo":1}');
526
});
527
528
+ it('should handle deduped props of re-used elements in fragments (same-chunk reference)', async () => {
529
+ let resolveFooClientComponentChunk;
530
+
531
+ const FooClient = clientExports(
532
+ function Foo({children, item}) {
533
+ return children;
534
+ },
535
+ '1',
536
+ '/foo.js',
537
+ new Promise(resolve => (resolveFooClientComponentChunk = resolve)),
538
+ );
539
+
540
+ const shared = <div />;
541
+
542
+ function Server() {
543
+ return (
544
+ <FooClient track={shared}>
545
+ <>{shared}</>
546
+ </FooClient>
547
+ );
548
+ }
549
+
550
+ const stream = await serverAct(() =>
551
+ ReactServerDOMServer.renderToReadableStream(<Server />, webpackMap),
552
+ );
553
+
554
+ function ClientRoot({response}) {
555
+ return use(response);
556
+ }
557
+
558
+ const response = ReactServerDOMClient.createFromReadableStream(stream);
559
+ const container = document.createElement('div');
560
+ const root = ReactDOMClient.createRoot(container);
561
+
562
+ await act(() => {
563
+ root.render(<ClientRoot response={response} />);
564
+ });
565
+
566
+ expect(container.innerHTML).toBe('');
567
+
568
+ await act(() => {
569
+ resolveFooClientComponentChunk();
570
+ });
571
+
572
+ expect(container.innerHTML).toBe('<div></div>');
573
+ });
574
+
575
+ it('should handle deduped props of re-used elements in server components (cross-chunk reference)', async () => {
576
+ let resolveFooClientComponentChunk;
577
+
578
+ function PassthroughServerComponent({children}) {
579
+ return children;
580
+ }
581
+
582
+ const FooClient = clientExports(
583
+ function Foo({children, item}) {
584
+ return children;
585
+ },
586
+ '1',
587
+ '/foo.js',
588
+ new Promise(resolve => (resolveFooClientComponentChunk = resolve)),
589
+ );
590
+
591
+ const shared = <div />;
592
+
593
+ function Server() {
594
+ return (
595
+ <FooClient track={shared}>
596
+ <PassthroughServerComponent>{shared}</PassthroughServerComponent>
597
+ </FooClient>
598
+ );
599
+ }
600
+
601
+ const stream = await serverAct(() =>
602
+ ReactServerDOMServer.renderToReadableStream(<Server />, webpackMap),
603
+ );
604
+
605
+ function ClientRoot({response}) {
606
+ return use(response);
607
+ }
608
+
609
+ const response = ReactServerDOMClient.createFromReadableStream(stream);
610
+ const container = document.createElement('div');
611
+ const root = ReactDOMClient.createRoot(container);
612
+
613
+ await act(() => {
614
+ root.render(<ClientRoot response={response} />);
615
+ });
616
+
617
+ expect(container.innerHTML).toBe('');
618
+
619
+ await act(() => {
620
+ resolveFooClientComponentChunk();
621
+ });
622
+
623
+ expect(container.innerHTML).toBe('<div></div>');
624
+ });
625
+
626
it('should progressively reveal server components', async () => {
627
let reportedErrors = [];
628