Fix: Multiple hydration errors in same render (#23273)

I made a minor mistake in the original onRecoverableError PR that
only surfaces if there are hydration errors in two different Suspense
boundaries in the same render. This fixes it and adds a unit test.
This commit is contained in:
Andrew Clark
2022-02-10 08:40:00 -08:00
committed by GitHub
parent efd8f6442d
commit 64223fed82
3 changed files with 70 additions and 6 deletions
@@ -2191,4 +2191,68 @@ describe('ReactDOMFizzServer', () => {
// UI looks normal
expect(container.textContent).toEqual('AB');
});
// @gate experimental
it('logs multiple hydration errors in the same render', async () => {
let isClient = false;
function subscribe() {
return () => {};
}
function getClientSnapshot() {
return 'Yay!';
}
function getServerSnapshot() {
if (isClient) {
throw new Error('Hydration error');
}
return 'Yay!';
}
function Child({label}) {
// This will throw during client hydration. Only reason to use
// useSyncExternalStore in this test is because getServerSnapshot has the
// ability to observe whether we're hydrating.
useSyncExternalStore(subscribe, getClientSnapshot, getServerSnapshot);
Scheduler.unstable_yieldValue(label);
return label;
}
function App() {
return (
<>
<Suspense fallback="Loading...">
<Child label="A" />
</Suspense>
<Suspense fallback="Loading...">
<Child label="B" />
</Suspense>
</>
);
}
await act(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
pipe(writable);
});
expect(Scheduler).toHaveYielded(['A', 'B']);
// Hydrate the tree. Child will throw during hydration, but not when it
// falls back to client rendering.
isClient = true;
ReactDOM.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.unstable_yieldValue(
'Logged recoverable error: ' + error.message,
);
},
});
expect(Scheduler).toFlushAndYield([
'A',
'B',
'Logged recoverable error: Hydration error',
'Logged recoverable error: Hydration error',
]);
});
});
@@ -920,11 +920,11 @@ function recoverFromConcurrentError(root, errorRetryLanes) {
}
export function queueRecoverableErrors(errors: Array<mixed>) {
if (workInProgressRootConcurrentErrors === null) {
if (workInProgressRootRecoverableErrors === null) {
workInProgressRootRecoverableErrors = errors;
} else {
workInProgressRootConcurrentErrors = workInProgressRootConcurrentErrors.push.apply(
workInProgressRootConcurrentErrors,
workInProgressRootRecoverableErrors.push.apply(
workInProgressRootRecoverableErrors,
errors,
);
}
@@ -920,11 +920,11 @@ function recoverFromConcurrentError(root, errorRetryLanes) {
}
export function queueRecoverableErrors(errors: Array<mixed>) {
if (workInProgressRootConcurrentErrors === null) {
if (workInProgressRootRecoverableErrors === null) {
workInProgressRootRecoverableErrors = errors;
} else {
workInProgressRootConcurrentErrors = workInProgressRootConcurrentErrors.push.apply(
workInProgressRootConcurrentErrors,
workInProgressRootRecoverableErrors.push.apply(
workInProgressRootRecoverableErrors,
errors,
);
}