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