@samitouri / QOS-React-2 / commits / edf6eac8a1

Don't cut off the tail of a SuspenseList if hydrating (#18854)

Sebastian Markbåge committed May 6, 2020 at 23:51 UTC edf6eac8a181860fd8a2d076a43806f1237495a1
3 files changed +105
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+93
@@ -1698,6 +1698,99 @@ describe('ReactDOMServerPartialHydration', () => {
1698 expect(container.textContent).toBe('ABC');
1699 });
1700
1701 + // @gate experimental
1702 + it('clears server boundaries when SuspenseList does a second pass', async () => {
1703 + let suspend = false;
1704 + let resolve;
1705 + const promise = new Promise(resolvePromise => (resolve = resolvePromise));
1706 +
1707 + const ref = React.createRef();
1708 +
1709 + function Child({children}) {
1710 + if (suspend) {
1711 + throw promise;
1712 + } else {
1713 + return children;
1714 + }
1715 + }
1716 +
1717 + function Before() {
1718 + Scheduler.unstable_yieldValue('Before');
1719 + return null;
1720 + }
1721 +
1722 + function After() {
1723 + Scheduler.unstable_yieldValue('After');
1724 + return null;
1725 + }
1726 +
1727 + function FirstRow() {
1728 + return (
1729 + <>
1730 + <Before />
1731 + <Suspense fallback="Loading A">
1732 + <span>A</span>
1733 + </Suspense>
1734 + <After />
1735 + </>
1736 + );
1737 + }
1738 +
1739 + function App() {
1740 + return (
1741 + <Suspense fallback={null}>
1742 + <SuspenseList revealOrder="forwards" tail="hidden">
1743 + <FirstRow />
1744 + <Suspense fallback="Loading B">
1745 + <Child>
1746 + <span ref={ref}>B</span>
1747 + </Child>
1748 + </Suspense>
1749 + </SuspenseList>
1750 + </Suspense>
1751 + );
1752 + }
1753 +
1754 + suspend = false;
1755 + const html = ReactDOMServer.renderToString(<App />);
1756 + expect(Scheduler).toHaveYielded(['Before', 'After']);
1757 +
1758 + const container = document.createElement('div');
1759 + container.innerHTML = html;
1760 +
1761 + const b = container.getElementsByTagName('span')[1];
1762 + expect(b.textContent).toBe('B');
1763 +
1764 + const root = ReactDOM.createRoot(container, {hydrate: true});
1765 +
1766 + // Increase hydration priority to higher than "offscreen".
1767 + ReactDOM.unstable_scheduleHydration(b);
1768 +
1769 + suspend = true;
1770 +
1771 + await act(async () => {
1772 + root.render(<App />);
1773 + expect(Scheduler).toFlushAndYieldThrough(['Before']);
1774 + // This took a long time to render.
1775 + Scheduler.unstable_advanceTime(1000);
1776 + expect(Scheduler).toFlushAndYield(['After']);
1777 + // This will cause us to skip the second row completely.
1778 + });
1779 +
1780 + // We haven't hydrated the second child but the placeholder is still in the list.
1781 + expect(ref.current).toBe(null);
1782 + expect(container.textContent).toBe('AB');
1783 +
1784 + suspend = false;
1785 + await act(async () => {
1786 + // Resolve the boundary to be in its resolved final state.
1787 + await resolve();
1788 + });
1789 +
1790 + expect(container.textContent).toBe('AB');
1791 + expect(ref.current).toBe(b);
1792 + });
1793 +
1794 // @gate experimental
1795 it('can client render nested boundaries', async () => {
1796 let suspend = false;
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+6
@@ -118,6 +118,7 @@ import {
118 prepareToHydrateHostSuspenseInstance,
119 popHydrationState,
120 resetHydrationState,
121 + getIsHydrating,
122 } from './ReactFiberHydrationContext.new';
123 import {
124 enableSchedulerTracing,
@@ -579,6 +580,11 @@ function cutOffTailIfNeeded(
580 renderState: SuspenseListRenderState,
581 hasRenderedATailFallback: boolean,
582 ) {
583 + if (getIsHydrating()) {
584 + // If we're hydrating, we should consume as many items as we can
585 + // so we don't leave any behind.
586 + return;
587 + }
588 switch (renderState.tailMode) {
589 case 'hidden': {
590 // Any insertions at the end of the tail list after this point
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+6
@@ -115,6 +115,7 @@ import {
115 prepareToHydrateHostSuspenseInstance,
116 popHydrationState,
117 resetHydrationState,
118 + getIsHydrating,
119 } from './ReactFiberHydrationContext.old';
120 import {
121 enableSchedulerTracing,
@@ -575,6 +576,11 @@ function cutOffTailIfNeeded(
576 renderState: SuspenseListRenderState,
577 hasRenderedATailFallback: boolean,
578 ) {
579 + if (getIsHydrating()) {
580 + // If we're hydrating, we should consume as many items as we can
581 + // so we don't leave any behind.
582 + return;
583 + }
584 switch (renderState.tailMode) {
585 case 'hidden': {
586 // Any insertions at the end of the tail list after this point