Refine isFiberSuspenseAndTimedOut (#18184)
Dominic Gannaway committed
Mar 2, 2020 at 14:15 UTC
8cb2fb21eb4e931b80d8b0502059061891ac4563
2 files changed
+76
-2
packages/react-reconciler/src/ReactFiberScope.js
+6
-1
@@ -27,7 +27,12 @@ import {
27
import {enableScopeAPI} from 'shared/ReactFeatureFlags';
28
29
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
+ );
36
}
37
38
function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+70
-1
@@ -13,6 +13,8 @@ import {createEventTarget} from 'dom-event-testing-library';
13
14
let React;
15
let ReactFeatureFlags;
16
+let ReactDOMServer;
17
+let Scheduler;
18
19
describe('ReactScope', () => {
20
beforeEach(() => {
@@ -21,6 +23,7 @@ describe('ReactScope', () => {
23
ReactFeatureFlags.enableScopeAPI = true;
24
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
25
React = require('react');
26
+ Scheduler = require('scheduler');
27
});
28
29
if (!__EXPERIMENTAL__) {
@@ -34,6 +37,7 @@ describe('ReactScope', () => {
37
38
beforeEach(() => {
39
ReactDOM = require('react-dom');
40
+ ReactDOMServer = require('react-dom/server');
41
container = document.createElement('div');
42
document.body.appendChild(container);
43
});
@@ -208,7 +212,6 @@ describe('ReactScope', () => {
212
213
it('scopes support server-side rendering and hydration', () => {
214
const TestScope = React.unstable_createScope();
211
- const ReactDOMServer = require('react-dom/server');
215
const scopeRef = React.createRef();
216
const divRef = React.createRef();
217
const spanRef = React.createRef();
@@ -306,6 +309,72 @@ describe('ReactScope', () => {
309
ReactDOM.render(null, container);
310
expect(scopeRef.current).toBe(null);
311
});
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
+ });
378
});
379
380
describe('ReactTestRenderer', () => {