Skip to content

Commit 8cb2fb2

Browse files
authored
Refine isFiberSuspenseAndTimedOut (#18184)
1 parent dbc7b9f commit 8cb2fb2

File tree

2 files changed

+76
-2
lines changed

2 files changed

+76
-2
lines changed

packages/react-reconciler/src/ReactFiberScope.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ import {
2727
import {enableScopeAPI} from 'shared/ReactFeatureFlags';
2828

2929
function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
30-
return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
30+
const memoizedState = fiber.memoizedState;
31+
return (
32+
fiber.tag === SuspenseComponent &&
33+
memoizedState !== null &&
34+
memoizedState.dehydrated === null
35+
);
3136
}
3237

3338
function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {

packages/react-reconciler/src/__tests__/ReactScope-test.internal.js

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {createEventTarget} from 'dom-event-testing-library';
1313

1414
let React;
1515
let ReactFeatureFlags;
16+
let ReactDOMServer;
17+
let Scheduler;
1618

1719
describe('ReactScope', () => {
1820
beforeEach(() => {
@@ -21,6 +23,7 @@ describe('ReactScope', () => {
2123
ReactFeatureFlags.enableScopeAPI = true;
2224
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
2325
React = require('react');
26+
Scheduler = require('scheduler');
2427
});
2528

2629
if (!__EXPERIMENTAL__) {
@@ -34,6 +37,7 @@ describe('ReactScope', () => {
3437

3538
beforeEach(() => {
3639
ReactDOM = require('react-dom');
40+
ReactDOMServer = require('react-dom/server');
3741
container = document.createElement('div');
3842
document.body.appendChild(container);
3943
});
@@ -208,7 +212,6 @@ describe('ReactScope', () => {
208212

209213
it('scopes support server-side rendering and hydration', () => {
210214
const TestScope = React.unstable_createScope();
211-
const ReactDOMServer = require('react-dom/server');
212215
const scopeRef = React.createRef();
213216
const divRef = React.createRef();
214217
const spanRef = React.createRef();
@@ -306,6 +309,72 @@ describe('ReactScope', () => {
306309
ReactDOM.render(null, container);
307310
expect(scopeRef.current).toBe(null);
308311
});
312+
313+
it('correctly works with suspended boundaries that are hydrated', async () => {
314+
let suspend = false;
315+
let resolve;
316+
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
317+
const ref = React.createRef();
318+
const TestScope = React.unstable_createScope();
319+
const scopeRef = React.createRef();
320+
const testScopeQuery = (type, props) => true;
321+
322+
function Child() {
323+
if (suspend) {
324+
throw promise;
325+
} else {
326+
return 'Hello';
327+
}
328+
}
329+
330+
function App() {
331+
return (
332+
<div>
333+
<TestScope ref={scopeRef}>
334+
<React.Suspense fallback="Loading...">
335+
<span ref={ref}>
336+
<Child />
337+
</span>
338+
</React.Suspense>
339+
</TestScope>
340+
</div>
341+
);
342+
}
343+
344+
// First we render the final HTML. With the streaming renderer
345+
// this may have suspense points on the server but here we want
346+
// to test the completed HTML. Don't suspend on the server.
347+
suspend = false;
348+
let finalHTML = ReactDOMServer.renderToString(<App />);
349+
350+
let container2 = document.createElement('div');
351+
container2.innerHTML = finalHTML;
352+
353+
let span = container2.getElementsByTagName('span')[0];
354+
355+
// On the client we don't have all data yet but we want to start
356+
// hydrating anyway.
357+
suspend = true;
358+
let root = ReactDOM.createRoot(container2, {hydrate: true});
359+
root.render(<App />);
360+
Scheduler.unstable_flushAll();
361+
jest.runAllTimers();
362+
363+
// This should not cause a runtime exception, see:
364+
// https://github.com/facebook/react/pull/18184
365+
scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
366+
expect(ref.current).toBe(null);
367+
368+
// Resolving the promise should continue hydration
369+
suspend = false;
370+
resolve();
371+
await promise;
372+
Scheduler.unstable_flushAll();
373+
jest.runAllTimers();
374+
375+
// We should now have hydrated with a ref on the existing span.
376+
expect(ref.current).toBe(span);
377+
});
309378
});
310379

311380
describe('ReactTestRenderer', () => {

0 commit comments

Comments
 (0)