616
assertLog([]);
617
expect(root).toMatchRenderedOutput(<div>Final</div>);
618
});
619
+
620
+ // @gate enableUseDeferredValueInitialArg
621
+ // @gate enableOffscreen
622
+ it('useDeferredValue can prerender the initial value inside a hidden tree', async () => {
623
+ function App({text}) {
624
+ const renderedText = useDeferredValue(text, `Preview [${text}]`);
625
+ return (
626
+ <div>
627
+ <Text text={renderedText} />
628
+ </div>
629
+ );
630
+ }
631
+
632
+ let revealContent;
633
+ function Container({children}) {
634
+ const [shouldShow, setState] = useState(false);
635
+ revealContent = () => setState(true);
636
+ return (
637
+ <Offscreen mode={shouldShow ? 'visible' : 'hidden'}>
638
+ {children}
639
+ </Offscreen>
640
+ );
641
+ }
642
+
643
+ const root = ReactNoop.createRoot();
644
+
645
+ // Prerender some content
646
+ await act(() => {
647
+ root.render(
648
+ <Container>
649
+ <App text="A" />
650
+ </Container>,
651
+ );
652
+ });
653
+ assertLog(['Preview [A]', 'A']);
654
+ expect(root).toMatchRenderedOutput(<div hidden={true}>A</div>);
655
+
656
+ await act(async () => {
657
+ // While the tree is still hidden, update the pre-rendered tree.
658
+ root.render(
659
+ <Container>
660
+ <App text="B" />
661
+ </Container>,
662
+ );
663
+ // We should switch to pre-rendering the new preview.
664
+ await waitForPaint(['Preview [B]']);
665
+ expect(root).toMatchRenderedOutput(<div hidden={true}>Preview [B]</div>);
666
+
667
+ // Before the prerender is complete, reveal the hidden tree. Because we
668
+ // consider revealing a hidden tree to be the same as mounting a new one,
669
+ // we should not skip the preview state.
670
+ revealContent();
671
+ // Because the preview state was already prerendered, we can reveal it
672
+ // without any addditional work.
673
+ await waitForPaint([]);
674
+ expect(root).toMatchRenderedOutput(<div>Preview [B]</div>);
675
+ });
676
+ // Finally, finish rendering the final value.
677
+ assertLog(['B']);
678
+ expect(root).toMatchRenderedOutput(<div>B</div>);
679
+ });
680
+
681
+ // @gate enableUseDeferredValueInitialArg
682
+ // @gate enableOffscreen
683
+ it(
684
+ 'useDeferredValue skips the preview state when revealing a hidden tree ' +
685
+ 'if the final value is referentially identical',
686
+ async () => {
687
+ function App({text}) {
688
+ const renderedText = useDeferredValue(text, `Preview [${text}]`);
689
+ return (
690
+ <div>
691
+ <Text text={renderedText} />
692
+ </div>
693
+ );
694
+ }
695
+
696
+ function Container({text, shouldShow}) {
697
+ return (
698
+ <Offscreen mode={shouldShow ? 'visible' : 'hidden'}>
699
+ <App text={text} />
700
+ </Offscreen>
701
+ );
702
+ }
703
+
704
+ const root = ReactNoop.createRoot();
705
+
706
+ // Prerender some content
707
+ await act(() => root.render(<Container text="A" shouldShow={false} />));
708
+ assertLog(['Preview [A]', 'A']);
709
+ expect(root).toMatchRenderedOutput(<div hidden={true}>A</div>);
710
+
711
+ // Reveal the prerendered tree. Because the final value is referentially
712
+ // equal to what was already prerendered, we can skip the preview state
713
+ // and go straight to the final one. The practical upshot of this is
714
+ // that we can completely prerender the final value without having to
715
+ // do additional rendering work when the tree is revealed.
716
+ await act(() => root.render(<Container text="A" shouldShow={true} />));
717
+ assertLog(['A']);
718
+ expect(root).toMatchRenderedOutput(<div>A</div>);
719
+ },
720
+ );
721
+
722
+ // @gate enableUseDeferredValueInitialArg
723
+ // @gate enableOffscreen
724
+ it(
725
+ 'useDeferredValue does not skip the preview state when revealing a ' +
726
+ 'hidden tree if the final value is different from the currently rendered one',
727
+ async () => {
728
+ function App({text}) {
729
+ const renderedText = useDeferredValue(text, `Preview [${text}]`);
730
+ return (
731
+ <div>
732
+ <Text text={renderedText} />
733
+ </div>
734
+ );
735
+ }
736
+
737
+ function Container({text, shouldShow}) {
738
+ return (
739
+ <Offscreen mode={shouldShow ? 'visible' : 'hidden'}>
740
+ <App text={text} />
741
+ </Offscreen>
742
+ );
743
+ }
744
+
745
+ const root = ReactNoop.createRoot();
746
+
747
+ // Prerender some content
748
+ await act(() => root.render(<Container text="A" shouldShow={false} />));
749
+ assertLog(['Preview [A]', 'A']);
750
+ expect(root).toMatchRenderedOutput(<div hidden={true}>A</div>);
751
+
752
+ // Reveal the prerendered tree. Because the final value is different from
753
+ // what was already prerendered, we can't bail out. Since we treat
754
+ // revealing a hidden tree the same as a new mount, show the preview state
755
+ // before switching to the final one.
756
+ await act(async () => {
757
+ root.render(<Container text="B" shouldShow={true} />);
758
+ // First commit the preview state
759
+ await waitForPaint(['Preview [B]']);
760
+ expect(root).toMatchRenderedOutput(<div>Preview [B]</div>);
761
+ });
762
+ // Then switch to the final state
763
+ assertLog(['B']);
764
+ expect(root).toMatchRenderedOutput(<div>B</div>);
765
+ },
766
+ );
767
+
768
+ // @gate enableOffscreen
769
+ it(
770
+ 'useDeferredValue does not show "previous" value when revealing a hidden ' +
771
+ 'tree (no initial value)',
772
+ async () => {
773
+ function App({text}) {
774
+ const renderedText = useDeferredValue(text);
775
+ return (
776
+ <div>
777
+ <Text text={renderedText} />
778
+ </div>
779
+ );
780
+ }
781
+
782
+ function Container({text, shouldShow}) {
783
+ return (
784
+ <Offscreen mode={shouldShow ? 'visible' : 'hidden'}>
785
+ <App text={text} />
786
+ </Offscreen>
787
+ );
788
+ }
789
+
790
+ const root = ReactNoop.createRoot();
791
+
792
+ // Prerender some content
793
+ await act(() => root.render(<Container text="A" shouldShow={false} />));
794
+ assertLog(['A']);
795
+ expect(root).toMatchRenderedOutput(<div hidden={true}>A</div>);
796
+
797
+ // Update the prerendered tree and reveal it at the same time. Even though
798
+ // this is a sync update, we should update B immediately rather than stay
799
+ // on the old value (A), because conceptually this is a new tree.
800
+ await act(() => root.render(<Container text="B" shouldShow={true} />));
801
+ assertLog(['B']);
802
+ expect(root).toMatchRenderedOutput(<div>B</div>);
803
+ },
804
+ );
805
});