@samitouri / QOS-React-2 / commits / 0fb747f368

Add LegacyHidden to server renderer (#18919)

* Add LegacyHidden to server renderer When the tree is hidden, the server renderer renders nothing. The contents will be completely client rendered. When the tree is visible it acts like a fragment. The future streaming server renderer may want to pre-render these trees and send them down in chunks, as with Suspense boundaries. * Force client render, even at Offscreen pri

Andrew Clark committed May 14, 2020 at 11:28 UTC 0fb747f368635933f27d296be96c8edcf99cbdbc
3 files changed +110 -1
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+89
@@ -2809,4 +2809,93 @@ describe('ReactDOMServerPartialHydration', () => {
2809 // Now we're hydrated.
2810 expect(ref.current).not.toBe(null);
2811 });
2812 +
2813 + // @gate experimental
2814 + // @gate new
2815 + it('renders a hidden LegacyHidden component', async () => {
2816 + const LegacyHidden = React.unstable_LegacyHidden;
2817 +
2818 + const ref = React.createRef();
2819 +
2820 + function App() {
2821 + return (
2822 + <LegacyHidden mode="hidden">
2823 + <span ref={ref}>Hidden child</span>
2824 + </LegacyHidden>
2825 + );
2826 + }
2827 +
2828 + const finalHTML = ReactDOMServer.renderToString(<App />);
2829 +
2830 + const container = document.createElement('div');
2831 + container.innerHTML = finalHTML;
2832 +
2833 + const span = container.getElementsByTagName('span')[0];
2834 + expect(span).toBe(undefined);
2835 +
2836 + const root = ReactDOM.createRoot(container, {hydrate: true});
2837 + root.render(<App />);
2838 + Scheduler.unstable_flushAll();
2839 + expect(ref.current.innerHTML).toBe('Hidden child');
2840 + });
2841 +
2842 + // @gate experimental
2843 + // @gate new
2844 + it('renders a hidden LegacyHidden component inside a Suspense boundary', async () => {
2845 + const LegacyHidden = React.unstable_LegacyHidden;
2846 +
2847 + const ref = React.createRef();
2848 +
2849 + function App() {
2850 + return (
2851 + <Suspense fallback="Loading...">
2852 + <LegacyHidden mode="hidden">
2853 + <span ref={ref}>Hidden child</span>
2854 + </LegacyHidden>
2855 + </Suspense>
2856 + );
2857 + }
2858 +
2859 + const finalHTML = ReactDOMServer.renderToString(<App />);
2860 +
2861 + const container = document.createElement('div');
2862 + container.innerHTML = finalHTML;
2863 +
2864 + const span = container.getElementsByTagName('span')[0];
2865 + expect(span).toBe(undefined);
2866 +
2867 + const root = ReactDOM.createRoot(container, {hydrate: true});
2868 + root.render(<App />);
2869 + Scheduler.unstable_flushAll();
2870 + expect(ref.current.innerHTML).toBe('Hidden child');
2871 + });
2872 +
2873 + // @gate experimental
2874 + // @gate new
2875 + it('renders a visible LegacyHidden component', async () => {
2876 + const LegacyHidden = React.unstable_LegacyHidden;
2877 +
2878 + const ref = React.createRef();
2879 +
2880 + function App() {
2881 + return (
2882 + <LegacyHidden mode="visible">
2883 + <span ref={ref}>Hidden child</span>
2884 + </LegacyHidden>
2885 + );
2886 + }
2887 +
2888 + const finalHTML = ReactDOMServer.renderToString(<App />);
2889 +
2890 + const container = document.createElement('div');
2891 + container.innerHTML = finalHTML;
2892 +
2893 + const span = container.getElementsByTagName('span')[0];
2894 +
2895 + const root = ReactDOM.createRoot(container, {hydrate: true});
2896 + root.render(<App />);
2897 + Scheduler.unstable_flushAll();
2898 + expect(ref.current).toBe(span);
2899 + expect(ref.current.innerHTML).toBe('Hidden child');
2900 + });
2901 });
packages/react-dom/src/server/ReactPartialRenderer.js
+13
@@ -42,6 +42,7 @@ import {
42 REACT_MEMO_TYPE,
43 REACT_FUNDAMENTAL_TYPE,
44 REACT_SCOPE_TYPE,
45 + REACT_LEGACY_HIDDEN_TYPE,
46 } from 'shared/ReactSymbols';
47
48 import {
@@ -1019,6 +1020,18 @@ class ReactDOMServerRenderer {
1020 }
1021
1022 switch (elementType) {
1023 + case REACT_LEGACY_HIDDEN_TYPE: {
1024 + if (!enableSuspenseServerRenderer) {
1025 + break;
1026 + }
1027 + if (((nextChild: any): ReactElement).props.mode === 'hidden') {
1028 + // In hidden mode, render nothing.
1029 + return '';
1030 + }
1031 + // Otherwise the tree is visible, so act like a fragment.
1032 + }
1033 + // Intentional fall through
1034 + // eslint-disable-next-line no-fallthrough
1035 case REACT_DEBUG_TRACING_MODE_TYPE:
1036 case REACT_STRICT_MODE_TYPE:
1037 case REACT_PROFILER_TYPE:
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+8 -1
@@ -165,6 +165,7 @@ import {
165 reenterHydrationStateFromDehydratedSuspenseInstance,
166 resetHydrationState,
167 tryToClaimNextHydratableInstance,
168 + getIsHydrating,
169 warnIfHydrating,
170 } from './ReactFiberHydrationContext.new';
171 import {
@@ -574,7 +575,13 @@ function updateOffscreenComponent(
575 };
576 workInProgress.memoizedState = nextState;
577 pushRenderLanes(workInProgress, renderLanes);
577 - } else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
578 + } else if (
579 + !includesSomeLane(renderLanes, (OffscreenLane: Lane)) ||
580 + // Server renderer does not render hidden subtrees, so if we're hydrating
581 + // we should always bail out and schedule a subsequent render pass, to
582 + // force a client render. Even if we're already at Offscreen priority.
583 + (current === null && getIsHydrating())
584 + ) {
585 let nextBaseLanes;
586 if (prevState !== null) {
587 const prevBaseLanes = prevState.baseLanes;