main
js 107 lines 3.74 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Wakeable, SuspenseListTailMode} from 'shared/ReactTypes';
11 import type {Fiber} from './ReactInternalTypes';
12 import type {SuspenseInstance} from './ReactFiberConfig';
13 import type {Lane} from './ReactFiberLane';
14 import type {TreeContext} from './ReactFiberTreeContext';
15 import type {CapturedValue} from './ReactCapturedValue';
16
17 import {SuspenseComponent, SuspenseListComponent} from './ReactWorkTags';
18 import {NoFlags, DidCapture} from './ReactFiberFlags';
19 import {
20 isSuspenseInstancePending,
21 isSuspenseInstanceFallback,
22 } from './ReactFiberConfig';
23
24 // A null SuspenseState represents an unsuspended normal Suspense boundary.
25 // A non-null SuspenseState means that it is blocked for one reason or another.
26 // - A non-null dehydrated field means it's blocked pending hydration.
27 // - A non-null dehydrated field can use isSuspenseInstancePending or
28 // isSuspenseInstanceFallback to query the reason for being dehydrated.
29 // - A null dehydrated field means it's blocked by something suspending and
30 // we're currently showing a fallback instead.
31 export type SuspenseState = {
32 // If this boundary is still dehydrated, we store the SuspenseInstance
33 // here to indicate that it is dehydrated (flag) and for quick access
34 // to check things like isSuspenseInstancePending.
35 dehydrated: null | SuspenseInstance,
36 treeContext: null | TreeContext,
37 // Represents the lane we should attempt to hydrate a dehydrated boundary at.
38 // OffscreenLane is the default for dehydrated boundaries.
39 // NoLane is the default for normal boundaries, which turns into "normal" pri.
40 retryLane: Lane,
41 // Stashed Errors that happened while attempting to hydrate this boundary.
42 hydrationErrors: Array<CapturedValue<mixed>> | null,
43 };
44
45 export type SuspenseListRenderState = {
46 isBackwards: boolean,
47 // The currently rendering tail row.
48 rendering: null | Fiber,
49 // The absolute time when we started rendering the most recent tail row.
50 renderingStartTime: number,
51 // The last of the already rendered children.
52 last: null | Fiber,
53 // Remaining rows on the tail of the list.
54 tail: null | Fiber,
55 // Tail insertions setting.
56 tailMode: SuspenseListTailMode,
57 // Keep track of total number of forks during multiple passes
58 treeForkCount: number,
59 };
60
61 export type RetryQueue = Set<Wakeable>;
62
63 export function findFirstSuspended(row: Fiber): null | Fiber {
64 let node = row;
65 // $FlowFixMe[invalid-compare]
66 while (node !== null) {
67 if (node.tag === SuspenseComponent) {
68 const state: SuspenseState | null = node.memoizedState;
69 if (state !== null) {
70 const dehydrated: null | SuspenseInstance = state.dehydrated;
71 if (
72 dehydrated === null ||
73 isSuspenseInstancePending(dehydrated) ||
74 isSuspenseInstanceFallback(dehydrated)
75 ) {
76 return node;
77 }
78 }
79 } else if (
80 node.tag === SuspenseListComponent &&
81 // Independent revealOrder can't be trusted because it doesn't
82 // keep track of whether it suspended or not.
83 node.memoizedProps.revealOrder !== 'independent'
84 ) {
85 const didSuspend = (node.flags & DidCapture) !== NoFlags;
86 if (didSuspend) {
87 return node;
88 }
89 } else if (node.child !== null) {
90 node.child.return = node;
91 node = node.child;
92 continue;
93 }
94 if (node === row) {
95 return null;
96 }
97 while (node.sibling === null) {
98 if (node.return === null || node.return === row) {
99 return null;
100 }
101 node = node.return;
102 }
103 node.sibling.return = node.return;
104 node = node.sibling;
105 }
106 return null;
107 }