Don't cut off effects at end of list if hydrating (#18872)
Sebastian Markbåge committed
May 8, 2020 at 21:26 UTC
539527b642101fbfc0b4ba614d7e253905f46b08
3 files changed
+62
-3
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+58
-1
@@ -1699,7 +1699,7 @@ describe('ReactDOMServerPartialHydration', () => {
1699
});
1700
1701
// @gate experimental
1702
- it('clears server boundaries when SuspenseList does a second pass', async () => {
1702
+ it('clears server boundaries when SuspenseList runs out of time hydrating', async () => {
1703
let suspend = false;
1704
let resolve;
1705
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
@@ -1791,6 +1791,63 @@ describe('ReactDOMServerPartialHydration', () => {
1791
expect(ref.current).toBe(b);
1792
});
1793
1794
+ // @gate experimental
1795
+ it('clears server boundaries when SuspenseList suspends last row hydrating', async () => {
1796
+ let suspend = false;
1797
+ let resolve;
1798
+ const promise = new Promise(resolvePromise => (resolve = resolvePromise));
1799
+
1800
+ function Child({children}) {
1801
+ if (suspend) {
1802
+ throw promise;
1803
+ } else {
1804
+ return children;
1805
+ }
1806
+ }
1807
+
1808
+ function App() {
1809
+ return (
1810
+ <Suspense fallback={null}>
1811
+ <SuspenseList revealOrder="forwards" tail="hidden">
1812
+ <Suspense fallback="Loading A">
1813
+ <span>A</span>
1814
+ </Suspense>
1815
+ <Suspense fallback="Loading B">
1816
+ <Child>
1817
+ <span>B</span>
1818
+ </Child>
1819
+ </Suspense>
1820
+ </SuspenseList>
1821
+ </Suspense>
1822
+ );
1823
+ }
1824
+
1825
+ suspend = true;
1826
+ const html = ReactDOMServer.renderToString(<App />);
1827
+
1828
+ const container = document.createElement('div');
1829
+ container.innerHTML = html;
1830
+
1831
+ const root = ReactDOM.createRoot(container, {hydrate: true});
1832
+
1833
+ suspend = true;
1834
+
1835
+ await act(async () => {
1836
+ root.render(<App />);
1837
+ });
1838
+
1839
+ // We haven't hydrated the second child but the placeholder is still in the list.
1840
+ expect(container.textContent).toBe('ALoading B');
1841
+
1842
+ suspend = false;
1843
+ await act(async () => {
1844
+ // Resolve the boundary to be in its resolved final state.
1845
+ await resolve();
1846
+ });
1847
+
1848
+ expect(container.textContent).toBe('AB');
1849
+ });
1850
+
1851
// @gate experimental
1852
it('can client render nested boundaries', async () => {
1853
let suspend = false;
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+2
-1
@@ -1098,7 +1098,8 @@ function completeWork(
1098
if (
1099
renderState.tail === null &&
1100
renderState.tailMode === 'hidden' &&
1101
- !renderedTail.alternate
1101
+ !renderedTail.alternate &&
1102
+ !getIsHydrating() // We don't cut it if we're hydrating.
1103
) {
1104
// We need to delete the row we just rendered.
1105
// Reset the effect list to what it was before we rendered this
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+2
-1
@@ -1114,7 +1114,8 @@ function completeWork(
1114
if (
1115
renderState.tail === null &&
1116
renderState.tailMode === 'hidden' &&
1117
- !renderedTail.alternate
1117
+ !renderedTail.alternate &&
1118
+ !getIsHydrating() // We don't cut it if we're hydrating.
1119
) {
1120
// We need to delete the row we just rendered.
1121
// Reset the effect list to what it was before we rendered this