mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user