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
This commit is contained in:
Andrew Clark
2020-05-14 11:28:11 -07:00
committed by GitHub
parent b4a1a4980c
commit 0fb747f368
3 changed files with 110 additions and 1 deletions
@@ -2809,4 +2809,93 @@ describe('ReactDOMServerPartialHydration', () => {
// Now we're hydrated.
expect(ref.current).not.toBe(null);
});
// @gate experimental
// @gate new
it('renders a hidden LegacyHidden component', async () => {
const LegacyHidden = React.unstable_LegacyHidden;
const ref = React.createRef();
function App() {
return (
<LegacyHidden mode="hidden">
<span ref={ref}>Hidden child</span>
</LegacyHidden>
);
}
const finalHTML = ReactDOMServer.renderToString(<App />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
const span = container.getElementsByTagName('span')[0];
expect(span).toBe(undefined);
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
Scheduler.unstable_flushAll();
expect(ref.current.innerHTML).toBe('Hidden child');
});
// @gate experimental
// @gate new
it('renders a hidden LegacyHidden component inside a Suspense boundary', async () => {
const LegacyHidden = React.unstable_LegacyHidden;
const ref = React.createRef();
function App() {
return (
<Suspense fallback="Loading...">
<LegacyHidden mode="hidden">
<span ref={ref}>Hidden child</span>
</LegacyHidden>
</Suspense>
);
}
const finalHTML = ReactDOMServer.renderToString(<App />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
const span = container.getElementsByTagName('span')[0];
expect(span).toBe(undefined);
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
Scheduler.unstable_flushAll();
expect(ref.current.innerHTML).toBe('Hidden child');
});
// @gate experimental
// @gate new
it('renders a visible LegacyHidden component', async () => {
const LegacyHidden = React.unstable_LegacyHidden;
const ref = React.createRef();
function App() {
return (
<LegacyHidden mode="visible">
<span ref={ref}>Hidden child</span>
</LegacyHidden>
);
}
const finalHTML = ReactDOMServer.renderToString(<App />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
const span = container.getElementsByTagName('span')[0];
const root = ReactDOM.createRoot(container, {hydrate: true});
root.render(<App />);
Scheduler.unstable_flushAll();
expect(ref.current).toBe(span);
expect(ref.current.innerHTML).toBe('Hidden child');
});
});
+13
View File
@@ -42,6 +42,7 @@ import {
REACT_MEMO_TYPE,
REACT_FUNDAMENTAL_TYPE,
REACT_SCOPE_TYPE,
REACT_LEGACY_HIDDEN_TYPE,
} from 'shared/ReactSymbols';
import {
@@ -1019,6 +1020,18 @@ class ReactDOMServerRenderer {
}
switch (elementType) {
case REACT_LEGACY_HIDDEN_TYPE: {
if (!enableSuspenseServerRenderer) {
break;
}
if (((nextChild: any): ReactElement).props.mode === 'hidden') {
// In hidden mode, render nothing.
return '';
}
// Otherwise the tree is visible, so act like a fragment.
}
// Intentional fall through
// eslint-disable-next-line no-fallthrough
case REACT_DEBUG_TRACING_MODE_TYPE:
case REACT_STRICT_MODE_TYPE:
case REACT_PROFILER_TYPE:
@@ -165,6 +165,7 @@ import {
reenterHydrationStateFromDehydratedSuspenseInstance,
resetHydrationState,
tryToClaimNextHydratableInstance,
getIsHydrating,
warnIfHydrating,
} from './ReactFiberHydrationContext.new';
import {
@@ -574,7 +575,13 @@ function updateOffscreenComponent(
};
workInProgress.memoizedState = nextState;
pushRenderLanes(workInProgress, renderLanes);
} else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
} else if (
!includesSomeLane(renderLanes, (OffscreenLane: Lane)) ||
// Server renderer does not render hidden subtrees, so if we're hydrating
// we should always bail out and schedule a subsequent render pass, to
// force a client render. Even if we're already at Offscreen priority.
(current === null && getIsHydrating())
) {
let nextBaseLanes;
if (prevState !== null) {
const prevBaseLanes = prevState.baseLanes;