@samitouri / QOS-React-1 / commits / 17f88c80ed

Implement ActivityInstance in FiberConfigDOM (#32842)

Stacked on #32851 and #32900. This implements the equivalent Configs for ActivityInstance as we have for SuspenseInstance. These can be implemented as comments but they don't have to be and can be implemented differently in the renderer. This seems like a lot duplication but it's actually ends mostly just calling the same methods underneath and the wrappers compiles out. This doesn't leave the Activity dehydrated yet. It just hydrates into it immediately.

Sebastian Markbåge committed Apr 22, 2025 at 19:44 UTC 17f88c80ed20b4e5f21255d9e1268542a2fbc1bd
16 files changed +362 -91
packages/react-dom-bindings/src/client/ReactDOMComponentTree.js
+27 -16
@@ -17,6 +17,7 @@ import type {
17 Container,
18 TextInstance,
19 Instance,
20 + ActivityInstance,
21 SuspenseInstance,
22 Props,
23 HoistableRoot,
@@ -30,9 +31,10 @@ import {
31 HostText,
32 HostRoot,
33 SuspenseComponent,
34 + ActivityComponent,
35 } from 'react-reconciler/src/ReactWorkTags';
36
35 -import {getParentSuspenseInstance} from './ReactFiberConfigDOM';
37 +import {getParentHydrationBoundary} from './ReactFiberConfigDOM';
38
39 import {enableScopeAPI} from 'shared/ReactFeatureFlags';
40
@@ -59,7 +61,12 @@ export function detachDeletedInstance(node: Instance): void {
61
62 export function precacheFiberNode(
63 hostInst: Fiber,
62 - node: Instance | TextInstance | SuspenseInstance | ReactScopeInstance,
64 + node:
65 + | Instance
66 + | TextInstance
67 + | SuspenseInstance
68 + | ActivityInstance
69 + | ReactScopeInstance,
70 ): void {
71 (node: any)[internalInstanceKey] = hostInst;
72 }
@@ -81,15 +88,16 @@ export function isContainerMarkedAsRoot(node: Container): boolean {
88
89 // Given a DOM node, return the closest HostComponent or HostText fiber ancestor.
90 // If the target node is part of a hydrated or not yet rendered subtree, then
84 -// this may also return a SuspenseComponent or HostRoot to indicate that.
91 +// this may also return a SuspenseComponent, ActivityComponent or HostRoot to
92 +// indicate that.
93 // Conceptually the HostRoot fiber is a child of the Container node. So if you
94 // pass the Container node as the targetNode, you will not actually get the
95 // HostRoot back. To get to the HostRoot, you need to pass a child of it.
88 -// The same thing applies to Suspense boundaries.
96 +// The same thing applies to Suspense and Activity boundaries.
97 export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
98 let targetInst = (targetNode: any)[internalInstanceKey];
99 if (targetInst) {
92 - // Don't return HostRoot or SuspenseComponent here.
100 + // Don't return HostRoot, SuspenseComponent or ActivityComponent here.
101 return targetInst;
102 }
103 // If the direct event target isn't a React owned DOM node, we need to look
@@ -129,8 +137,8 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
137 ) {
138 // Next we need to figure out if the node that skipped past is
139 // nested within a dehydrated boundary and if so, which one.
132 - let suspenseInstance = getParentSuspenseInstance(targetNode);
133 - while (suspenseInstance !== null) {
140 + let hydrationInstance = getParentHydrationBoundary(targetNode);
141 + while (hydrationInstance !== null) {
142 // We found a suspense instance. That means that we haven't
143 // hydrated it yet. Even though we leave the comments in the
144 // DOM after hydrating, and there are boundaries in the DOM
@@ -140,15 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
148 // Let's get the fiber associated with the SuspenseComponent
149 // as the deepest instance.
150 // $FlowFixMe[prop-missing]
143 - const targetSuspenseInst = suspenseInstance[internalInstanceKey];
144 - if (targetSuspenseInst) {
145 - return targetSuspenseInst;
151 + const targetFiber = hydrationInstance[internalInstanceKey];
152 + if (targetFiber) {
153 + return targetFiber;
154 }
155 // If we don't find a Fiber on the comment, it might be because
156 // we haven't gotten to hydrate it yet. There might still be a
157 // parent boundary that hasn't above this one so we need to find
158 // the outer most that is known.
151 - suspenseInstance = getParentSuspenseInstance(suspenseInstance);
159 + hydrationInstance = getParentHydrationBoundary(hydrationInstance);
160 // If we don't find one, then that should mean that the parent
161 // host component also hasn't hydrated yet. We can return it
162 // below since it will bail out on the isMounted check later.
@@ -176,6 +184,7 @@ export function getInstanceFromNode(node: Node): Fiber | null {
184 tag === HostComponent ||
185 tag === HostText ||
186 tag === SuspenseComponent ||
187 + tag === ActivityComponent ||
188 tag === HostHoistable ||
189 tag === HostSingleton ||
190 tag === HostRoot
@@ -211,15 +220,17 @@ export function getNodeFromInstance(inst: Fiber): Instance | TextInstance {
220 }
221
222 export function getFiberCurrentPropsFromNode(
214 - node: Container | Instance | TextInstance | SuspenseInstance,
223 + node:
224 + | Container
225 + | Instance
226 + | TextInstance
227 + | SuspenseInstance
228 + | ActivityInstance,
229 ): Props {
230 return (node: any)[internalPropsKey] || null;
231 }
232
219 -export function updateFiberProps(
220 - node: Instance | TextInstance | SuspenseInstance,
221 - props: Props,
222 -): void {
233 +export function updateFiberProps(node: Instance, props: Props): void {
234 (node: any)[internalPropsKey] = props;
235 }
236
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+152 -37
@@ -187,13 +187,20 @@ export type Container =
187 | interface extends DocumentFragment {_reactRootContainer?: FiberRoot};
188 export type Instance = Element;
189 export type TextInstance = Text;
190 -export interface SuspenseInstance extends Comment {
191 - _reactRetry?: () => void;
190 +
191 +declare class ActivityInterface extends Comment {}
192 +declare class SuspenseInterface extends Comment {
193 + _reactRetry: void | (() => void);
194 }
195 +
196 +export type ActivityInstance = ActivityInterface;
197 +export type SuspenseInstance = SuspenseInterface;
198 +
199 type FormStateMarkerInstance = Comment;
200 export type HydratableInstance =
201 | Instance
202 | TextInstance
203 + | ActivityInstance
204 | SuspenseInstance
205 | FormStateMarkerInstance;
206 export type PublicInstance = Element | Text;
@@ -226,6 +233,8 @@ type SelectionInformation = {
233
234 const SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
235
236 +const ACTIVITY_START_DATA = '&';
237 +const ACTIVITY_END_DATA = '/&';
238 const SUSPENSE_START_DATA = '$';
239 const SUSPENSE_END_DATA = '/$';
240 const SUSPENSE_PENDING_START_DATA = '$?';
@@ -947,7 +956,7 @@ export function appendChildToContainer(
956 export function insertBefore(
957 parentInstance: Instance,
958 child: Instance | TextInstance,
950 - beforeChild: Instance | TextInstance | SuspenseInstance,
959 + beforeChild: Instance | TextInstance | SuspenseInstance | ActivityInstance,
960 ): void {
961 if (supportsMoveBefore && child.parentNode !== null) {
962 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.
@@ -960,7 +969,7 @@ export function insertBefore(
969 export function insertInContainerBefore(
970 container: Container,
971 child: Instance | TextInstance,
963 - beforeChild: Instance | TextInstance | SuspenseInstance,
972 + beforeChild: Instance | TextInstance | SuspenseInstance | ActivityInstance,
973 ): void {
974 if (__DEV__) {
975 warnForReactChildrenConflict(container);
@@ -1024,14 +1033,14 @@ function dispatchAfterDetachedBlur(target: HTMLElement): void {
1033
1034 export function removeChild(
1035 parentInstance: Instance,
1027 - child: Instance | TextInstance | SuspenseInstance,
1036 + child: Instance | TextInstance | SuspenseInstance | ActivityInstance,
1037 ): void {
1038 parentInstance.removeChild(child);
1039 }
1040
1041 export function removeChildFromContainer(
1042 container: Container,
1034 - child: Instance | TextInstance | SuspenseInstance,
1043 + child: Instance | TextInstance | SuspenseInstance | ActivityInstance,
1044 ): void {
1045 let parentNode: DocumentFragment | Element;
1046 if (container.nodeType === DOCUMENT_NODE) {
@@ -1049,11 +1058,11 @@ export function removeChildFromContainer(
1058 parentNode.removeChild(child);
1059 }
1060
1052 -export function clearSuspenseBoundary(
1061 +function clearHydrationBoundary(
1062 parentInstance: Instance,
1054 - suspenseInstance: SuspenseInstance,
1063 + hydrationInstance: SuspenseInstance | ActivityInstance,
1064 ): void {
1056 - let node: Node = suspenseInstance;
1065 + let node: Node = hydrationInstance;
1066 // Delete all nodes within this suspense boundary.
1067 // There might be nested nodes so we need to keep track of how
1068 // deep we are and only break out when we're back on top.
@@ -1063,11 +1072,11 @@ export function clearSuspenseBoundary(
1072 parentInstance.removeChild(node);
1073 if (nextNode && nextNode.nodeType === COMMENT_NODE) {
1074 const data = ((nextNode: any).data: string);
1066 - if (data === SUSPENSE_END_DATA) {
1075 + if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {
1076 if (depth === 0) {
1077 parentInstance.removeChild(nextNode);
1078 // Retry if any event replaying was blocked on this.
1070 - retryIfBlockedOn(suspenseInstance);
1079 + retryIfBlockedOn(hydrationInstance);
1080 return;
1081 } else {
1082 depth--;
@@ -1075,7 +1084,8 @@ export function clearSuspenseBoundary(
1084 } else if (
1085 data === SUSPENSE_START_DATA ||
1086 data === SUSPENSE_PENDING_START_DATA ||
1078 - data === SUSPENSE_FALLBACK_START_DATA
1087 + data === SUSPENSE_FALLBACK_START_DATA ||
1088 + data === ACTIVITY_START_DATA
1089 ) {
1090 depth++;
1091 } else if (data === PREAMBLE_CONTRIBUTION_HTML) {
@@ -1102,12 +1112,26 @@ export function clearSuspenseBoundary(
1112 } while (node);
1113 // TODO: Warn, we didn't find the end comment boundary.
1114 // Retry if any event replaying was blocked on this.
1105 - retryIfBlockedOn(suspenseInstance);
1115 + retryIfBlockedOn(hydrationInstance);
1116 }
1117
1108 -export function clearSuspenseBoundaryFromContainer(
1109 - container: Container,
1118 +export function clearActivityBoundary(
1119 + parentInstance: Instance,
1120 + activityInstance: ActivityInstance,
1121 +): void {
1122 + clearHydrationBoundary(parentInstance, activityInstance);
1123 +}
1124 +
1125 +export function clearSuspenseBoundary(
1126 + parentInstance: Instance,
1127 suspenseInstance: SuspenseInstance,
1128 +): void {
1129 + clearHydrationBoundary(parentInstance, suspenseInstance);
1130 +}
1131 +
1132 +function clearHydrationBoundaryFromContainer(
1133 + container: Container,
1134 + hydrationInstance: SuspenseInstance | ActivityInstance,
1135 ): void {
1136 let parentNode: DocumentFragment | Element;
1137 if (container.nodeType === DOCUMENT_NODE) {
@@ -1122,13 +1146,27 @@ export function clearSuspenseBoundaryFromContainer(
1146 } else {
1147 parentNode = (container: any);
1148 }
1125 - clearSuspenseBoundary(parentNode, suspenseInstance);
1149 + clearHydrationBoundary(parentNode, hydrationInstance);
1150 // Retry if any event replaying was blocked on this.
1151 retryIfBlockedOn(container);
1152 }
1153
1130 -function hideOrUnhideSuspenseBoundary(
1154 +export function clearActivityBoundaryFromContainer(
1155 + container: Container,
1156 + activityInstance: ActivityInstance,
1157 +): void {
1158 + clearHydrationBoundaryFromContainer(container, activityInstance);
1159 +}
1160 +
1161 +export function clearSuspenseBoundaryFromContainer(
1162 + container: Container,
1163 suspenseInstance: SuspenseInstance,
1164 +): void {
1165 + clearHydrationBoundaryFromContainer(container, suspenseInstance);
1166 +}
1167 +
1168 +function hideOrUnhideDehydratedBoundary(
1169 + suspenseInstance: SuspenseInstance | ActivityInstance,
1170 isHidden: boolean,
1171 ) {
1172 let node: Node = suspenseInstance;
@@ -1178,8 +1216,10 @@ function hideOrUnhideSuspenseBoundary(
1216 } while (node);
1217 }
1218
1181 -export function hideSuspenseBoundary(suspenseInstance: SuspenseInstance): void {
1182 - hideOrUnhideSuspenseBoundary(suspenseInstance, true);
1219 +export function hideDehydratedBoundary(
1220 + suspenseInstance: SuspenseInstance,
1221 +): void {
1222 + hideOrUnhideDehydratedBoundary(suspenseInstance, true);
1223 }
1224
1225 export function hideInstance(instance: Instance): void {
@@ -1199,10 +1239,10 @@ export function hideTextInstance(textInstance: TextInstance): void {
1239 textInstance.nodeValue = '';
1240 }
1241
1202 -export function unhideSuspenseBoundary(
1203 - suspenseInstance: SuspenseInstance,
1242 +export function unhideDehydratedBoundary(
1243 + dehydratedInstance: SuspenseInstance | ActivityInstance,
1244 ): void {
1205 - hideOrUnhideSuspenseBoundary(suspenseInstance, false);
1245 + hideOrUnhideDehydratedBoundary(dehydratedInstance, false);
1246 }
1247
1248 export function unhideInstance(instance: Instance, props: Props): void {
@@ -3047,10 +3087,10 @@ export function canHydrateTextInstance(
3087 return ((instance: any): TextInstance);
3088 }
3089
3050 -export function canHydrateSuspenseInstance(
3090 +function canHydrateHydrationBoundary(
3091 instance: HydratableInstance,
3092 inRootOrSingleton: boolean,
3053 -): null | SuspenseInstance {
3093 +): null | SuspenseInstance | ActivityInstance {
3094 while (instance.nodeType !== COMMENT_NODE) {
3095 if (!inRootOrSingleton) {
3096 return null;
@@ -3061,8 +3101,42 @@ export function canHydrateSuspenseInstance(
3101 }
3102 instance = nextInstance;
3103 }
3064 - // This has now been refined to a suspense node.
3065 - return ((instance: any): SuspenseInstance);
3104 + // This has now been refined to a hydration boundary node.
3105 + return (instance: any);
3106 +}
3107 +
3108 +export function canHydrateActivityInstance(
3109 + instance: HydratableInstance,
3110 + inRootOrSingleton: boolean,
3111 +): null | ActivityInstance {
3112 + const hydratableInstance = canHydrateHydrationBoundary(
3113 + instance,
3114 + inRootOrSingleton,
3115 + );
3116 + if (
3117 + hydratableInstance !== null &&
3118 + hydratableInstance.data === ACTIVITY_START_DATA
3119 + ) {
3120 + return (hydratableInstance: any);
3121 + }
3122 + return null;
3123 +}
3124 +
3125 +export function canHydrateSuspenseInstance(
3126 + instance: HydratableInstance,
3127 + inRootOrSingleton: boolean,
3128 +): null | SuspenseInstance {
3129 + const hydratableInstance = canHydrateHydrationBoundary(
3130 + instance,
3131 + inRootOrSingleton,
3132 + );
3133 + if (
3134 + hydratableInstance !== null &&
3135 + hydratableInstance.data !== ACTIVITY_START_DATA
3136 + ) {
3137 + return (hydratableInstance: any);
3138 + }
3139 + return null;
3140 }
3141
3142 export function isSuspenseInstancePending(instance: SuspenseInstance): boolean {
@@ -3186,12 +3260,13 @@ function getNextHydratable(node: ?Node) {
3260 nodeData === SUSPENSE_START_DATA ||
3261 nodeData === SUSPENSE_FALLBACK_START_DATA ||
3262 nodeData === SUSPENSE_PENDING_START_DATA ||
3263 + nodeData === ACTIVITY_START_DATA ||
3264 nodeData === FORM_STATE_IS_MATCHING ||
3265 nodeData === FORM_STATE_IS_NOT_MATCHING
3266 ) {
3267 break;
3268 }
3194 - if (nodeData === SUSPENSE_END_DATA) {
3269 + if (nodeData === SUSPENSE_END_DATA || nodeData === ACTIVITY_END_DATA) {
3270 return null;
3271 }
3272 }
@@ -3230,6 +3305,12 @@ export function getFirstHydratableChildWithinContainer(
3305 return getNextHydratable(parentElement.firstChild);
3306 }
3307
3308 +export function getFirstHydratableChildWithinActivityInstance(
3309 + parentInstance: ActivityInstance,
3310 +): null | HydratableInstance {
3311 + return getNextHydratable(parentInstance.nextSibling);
3312 +}
3313 +
3314 export function getFirstHydratableChildWithinSuspenseInstance(
3315 parentInstance: SuspenseInstance,
3316 ): null | HydratableInstance {
@@ -3281,6 +3362,12 @@ export function describeHydratableInstanceForDevWarnings(
3362 props: getPropsFromElement((instance: any)),
3363 };
3364 } else if (instance.nodeType === COMMENT_NODE) {
3365 + if (instance.data === ACTIVITY_START_DATA) {
3366 + return {
3367 + type: 'Activity',
3368 + props: {},
3369 + };
3370 + }
3371 return {
3372 type: 'Suspense',
3373 props: {},
@@ -3372,6 +3459,13 @@ export function diffHydratedTextForDevWarnings(
3459 return null;
3460 }
3461
3462 +export function hydrateActivityInstance(
3463 + activityInstance: ActivityInstance,
3464 + internalInstanceHandle: Object,
3465 +) {
3466 + precacheFiberNode(internalInstanceHandle, activityInstance);
3467 +}
3468 +
3469 export function hydrateSuspenseInstance(
3470 suspenseInstance: SuspenseInstance,
3471 internalInstanceHandle: Object,
@@ -3379,10 +3473,10 @@ export function hydrateSuspenseInstance(
3473 precacheFiberNode(internalInstanceHandle, suspenseInstance);
3474 }
3475
3382 -export function getNextHydratableInstanceAfterSuspenseInstance(
3383 - suspenseInstance: SuspenseInstance,
3476 +function getNextHydratableInstanceAfterHydrationBoundary(
3477 + hydrationInstance: SuspenseInstance | ActivityInstance,
3478 ): null | HydratableInstance {
3385 - let node = suspenseInstance.nextSibling;
3479 + let node = hydrationInstance.nextSibling;
3480 // Skip past all nodes within this suspense boundary.
3481 // There might be nested nodes so we need to keep track of how
3482 // deep we are and only break out when we're back on top.
@@ -3390,7 +3484,7 @@ export function getNextHydratableInstanceAfterSuspenseInstance(
3484 while (node) {
3485 if (node.nodeType === COMMENT_NODE) {
3486 const data = ((node: any).data: string);
3393 - if (data === SUSPENSE_END_DATA) {
3487 + if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {
3488 if (depth === 0) {
3489 return getNextHydratableSibling((node: any));
3490 } else {
@@ -3399,7 +3493,8 @@ export function getNextHydratableInstanceAfterSuspenseInstance(
3493 } else if (
3494 data === SUSPENSE_START_DATA ||
3495 data === SUSPENSE_FALLBACK_START_DATA ||
3402 - data === SUSPENSE_PENDING_START_DATA
3496 + data === SUSPENSE_PENDING_START_DATA ||
3497 + data === ACTIVITY_START_DATA
3498 ) {
3499 depth++;
3500 }
@@ -3410,12 +3505,24 @@ export function getNextHydratableInstanceAfterSuspenseInstance(
3505 return null;
3506 }
3507
3508 +export function getNextHydratableInstanceAfterActivityInstance(
3509 + activityInstance: ActivityInstance,
3510 +): null | HydratableInstance {
3511 + return getNextHydratableInstanceAfterHydrationBoundary(activityInstance);
3512 +}
3513 +
3514 +export function getNextHydratableInstanceAfterSuspenseInstance(
3515 + suspenseInstance: SuspenseInstance,
3516 +): null | HydratableInstance {
3517 + return getNextHydratableInstanceAfterHydrationBoundary(suspenseInstance);
3518 +}
3519 +
3520 // Returns the SuspenseInstance if this node is a direct child of a
3521 // SuspenseInstance. I.e. if its previous sibling is a Comment with
3522 // SUSPENSE_x_START_DATA. Otherwise, null.
3416 -export function getParentSuspenseInstance(
3523 +export function getParentHydrationBoundary(
3524 targetInstance: Node,
3418 -): null | SuspenseInstance {
3525 +): null | SuspenseInstance | ActivityInstance {
3526 let node = targetInstance.previousSibling;
3527 // Skip past all nodes within this suspense boundary.
3528 // There might be nested nodes so we need to keep track of how
@@ -3427,14 +3534,15 @@ export function getParentSuspenseInstance(
3534 if (
3535 data === SUSPENSE_START_DATA ||
3536 data === SUSPENSE_FALLBACK_START_DATA ||
3430 - data === SUSPENSE_PENDING_START_DATA
3537 + data === SUSPENSE_PENDING_START_DATA ||
3538 + data === ACTIVITY_START_DATA
3539 ) {
3540 if (depth === 0) {
3433 - return ((node: any): SuspenseInstance);
3541 + return ((node: any): SuspenseInstance | ActivityInstance);
3542 } else {
3543 depth--;
3544 }
3437 - } else if (data === SUSPENSE_END_DATA) {
3545 + } else if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {
3546 depth++;
3547 }
3548 }
@@ -3448,6 +3556,13 @@ export function commitHydratedContainer(container: Container): void {
3556 retryIfBlockedOn(container);
3557 }
3558
3559 +export function commitHydratedActivityInstance(
3560 + activityInstance: ActivityInstance,
3561 +): void {
3562 + // Retry if any event replaying was blocked on this.
3563 + retryIfBlockedOn(activityInstance);
3564 +}
3565 +
3566 export function commitHydratedSuspenseInstance(
3567 suspenseInstance: SuspenseInstance,
3568 ): void {
packages/react-dom-bindings/src/events/ReactDOMEventListener.js
+27 -5
@@ -10,7 +10,11 @@
10 import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
11 import type {AnyNativeEvent} from '../events/PluginModuleType';
12 import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
13 -import type {Container, SuspenseInstance} from '../client/ReactFiberConfigDOM';
13 +import type {
14 + Container,
15 + ActivityInstance,
16 + SuspenseInstance,
17 +} from '../client/ReactFiberConfigDOM';
18 import type {DOMEventName} from '../events/DOMEventNames';
19
20 import {
@@ -22,9 +26,14 @@ import {attemptSynchronousHydration} from 'react-reconciler/src/ReactFiberReconc
26 import {
27 getNearestMountedFiber,
28 getContainerFromFiber,
29 + getActivityInstanceFromFiber,
30 getSuspenseInstanceFromFiber,
31 } from 'react-reconciler/src/ReactFiberTreeReflection';
27 -import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
32 +import {
33 + HostRoot,
34 + ActivityComponent,
35 + SuspenseComponent,
36 +} from 'react-reconciler/src/ReactWorkTags';
37 import {type EventSystemFlags, IS_CAPTURE_PHASE} from './EventSystemFlags';
38
39 import getEventTarget from './getEventTarget';
@@ -227,18 +236,18 @@ export function dispatchEvent(
236
237 export function findInstanceBlockingEvent(
238 nativeEvent: AnyNativeEvent,
230 -): null | Container | SuspenseInstance {
239 +): null | Container | SuspenseInstance | ActivityInstance {
240 const nativeEventTarget = getEventTarget(nativeEvent);
241 return findInstanceBlockingTarget(nativeEventTarget);
242 }
243
244 export let return_targetInst: null | Fiber = null;
245
237 -// Returns a SuspenseInstance or Container if it's blocked.
246 +// Returns a SuspenseInstance, ActivityInstance or Container if it's blocked.
247 // The return_targetInst field above is conceptually part of the return value.
248 export function findInstanceBlockingTarget(
249 targetNode: Node,
241 -): null | Container | SuspenseInstance {
250 +): null | Container | SuspenseInstance | ActivityInstance {
251 // TODO: Warn if _enabled is false.
252
253 return_targetInst = null;
@@ -265,6 +274,19 @@ export function findInstanceBlockingTarget(
274 // the whole system, dispatch the event without a target.
275 // TODO: Warn.
276 targetInst = null;
277 + } else if (tag === ActivityComponent) {
278 + const instance = getActivityInstanceFromFiber(nearestMounted);
279 + if (instance !== null) {
280 + // Queue the event to be replayed later. Abort dispatching since we
281 + // don't want this event dispatched twice through the event system.
282 + // TODO: If this is the first discrete event in the queue. Schedule an increased
283 + // priority for this boundary.
284 + return instance;
285 + }
286 + // This shouldn't happen, something went wrong but to avoid blocking
287 + // the whole system, dispatch the event without a target.
288 + // TODO: Warn.
289 + targetInst = null;
290 } else if (tag === HostRoot) {
291 const root: FiberRoot = nearestMounted.stateNode;
292 if (isRootDehydrated(root)) {
packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js
+30 -9
@@ -8,7 +8,11 @@
8 */
9
10 import type {AnyNativeEvent} from '../events/PluginModuleType';
11 -import type {Container, SuspenseInstance} from '../client/ReactFiberConfigDOM';
11 +import type {
12 + Container,
13 + ActivityInstance,
14 + SuspenseInstance,
15 +} from '../client/ReactFiberConfigDOM';
16 import type {DOMEventName} from '../events/DOMEventNames';
17 import type {EventSystemFlags} from './EventSystemFlags';
18 import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
@@ -21,6 +25,7 @@ import {
25 import {
26 getNearestMountedFiber,
27 getContainerFromFiber,
28 + getActivityInstanceFromFiber,
29 getSuspenseInstanceFromFiber,
30 } from 'react-reconciler/src/ReactFiberTreeReflection';
31 import {
@@ -33,7 +38,11 @@ import {
38 getClosestInstanceFromNode,
39 getFiberCurrentPropsFromNode,
40 } from '../client/ReactDOMComponentTree';
36 -import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
41 +import {
42 + HostRoot,
43 + ActivityComponent,
44 + SuspenseComponent,
45 +} from 'react-reconciler/src/ReactWorkTags';
46 import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities';
47 import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
48 import {dispatchReplayedFormAction} from './plugins/FormActionEventPlugin';
@@ -56,7 +65,7 @@ type PointerEvent = Event & {
65 };
66
67 type QueuedReplayableEvent = {
59 - blockedOn: null | Container | SuspenseInstance,
68 + blockedOn: null | Container | ActivityInstance | SuspenseInstance,
69 domEventName: DOMEventName,
70 eventSystemFlags: EventSystemFlags,
71 nativeEvent: AnyNativeEvent,
@@ -76,7 +85,7 @@ const queuedPointerCaptures: Map<number, QueuedReplayableEvent> = new Map();
85 // We could consider replaying selectionchange and touchmoves too.
86
87 type QueuedHydrationTarget = {
79 - blockedOn: null | Container | SuspenseInstance,
88 + blockedOn: null | Container | ActivityInstance | SuspenseInstance,
89 target: Node,
90 priority: EventPriority,
91 };
@@ -120,7 +129,7 @@ export function isDiscreteEventThatRequiresHydration(
129 }
130
131 function createQueuedReplayableEvent(
123 - blockedOn: null | Container | SuspenseInstance,
132 + blockedOn: null | Container | ActivityInstance | SuspenseInstance,
133 domEventName: DOMEventName,
134 eventSystemFlags: EventSystemFlags,
135 targetContainer: EventTarget,
@@ -170,7 +179,7 @@ export function clearIfContinuousEvent(
179
180 function accumulateOrCreateContinuousQueuedReplayableEvent(
181 existingQueuedEvent: null | QueuedReplayableEvent,
173 - blockedOn: null | Container | SuspenseInstance,
182 + blockedOn: null | Container | ActivityInstance | SuspenseInstance,
183 domEventName: DOMEventName,
184 eventSystemFlags: EventSystemFlags,
185 targetContainer: EventTarget,
@@ -212,7 +221,7 @@ function accumulateOrCreateContinuousQueuedReplayableEvent(
221 }
222
223 export function queueIfContinuousEvent(
215 - blockedOn: null | Container | SuspenseInstance,
224 + blockedOn: null | Container | ActivityInstance | SuspenseInstance,
225 domEventName: DOMEventName,
226 eventSystemFlags: EventSystemFlags,
227 targetContainer: EventTarget,
@@ -316,6 +325,18 @@ function attemptExplicitHydrationTarget(
325 attemptHydrationAtCurrentPriority(nearestMounted);
326 });
327
328 + return;
329 + }
330 + } else if (tag === ActivityComponent) {
331 + const instance = getActivityInstanceFromFiber(nearestMounted);
332 + if (instance !== null) {
333 + // We're blocked on hydrating this boundary.
334 + // Increase its priority.
335 + queuedTarget.blockedOn = instance;
336 + attemptHydrationAtPriority(queuedTarget.priority, () => {
337 + attemptHydrationAtCurrentPriority(nearestMounted);
338 + });
339 +
340 return;
341 }
342 } else if (tag === HostRoot) {
@@ -418,7 +439,7 @@ function replayUnblockedEvents() {
439
440 function scheduleCallbackIfUnblocked(
441 queuedEvent: QueuedReplayableEvent,
421 - unblocked: Container | SuspenseInstance,
442 + unblocked: Container | SuspenseInstance | ActivityInstance,
443 ) {
444 if (queuedEvent.blockedOn === unblocked) {
445 queuedEvent.blockedOn = null;
@@ -494,7 +515,7 @@ function scheduleReplayQueueIfNeeded(formReplayingQueue: FormReplayingQueue) {
515 }
516
517 export function retryIfBlockedOn(
497 - unblocked: Container | SuspenseInstance,
518 + unblocked: Container | SuspenseInstance | ActivityInstance,
519 ): void {
520 if (queuedFocus !== null) {
521 scheduleCallbackIfUnblocked(queuedFocus, unblocked);
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js
+1 -1
@@ -4,7 +4,7 @@
4 export const clientRenderBoundary =
5 '$RX=function(b,c,d,e,f){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),f&&(a.cstck=f),b._reactRetry&&b._reactRetry())};';
6 export const completeBoundary =
7 - '$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};';
7 + '$RC=function(b,d,e){d=document.getElementById(d);d.parentNode.removeChild(d);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var c=a.data;if("/$"===c||"/&"===c)if(0===f)break;else f--;else"$"!==c&&"$?"!==c&&"$!"!==c&&"&"!==c||f++}c=a.nextSibling;e.removeChild(a);a=c}while(a);for(;d.firstChild;)e.insertBefore(d.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};';
8 export const completeBoundaryWithStyles =
9 '$RM=new Map;\n$RR=function(t,u,y){function v(n){this._p=null;n()}for(var w=$RC,p=$RM,q=new Map,r=document,g,b,h=r.querySelectorAll("link[data-precedence],style[data-precedence]"),x=[],k=0;b=h[k++];)"not all"===b.getAttribute("media")?x.push(b):("LINK"===b.tagName&&p.set(b.getAttribute("href"),b),q.set(b.dataset.precedence,g=b));b=0;h=[];var l,a;for(k=!0;;){if(k){var e=y[b++];if(!e){k=!1;b=0;continue}var c=!1,m=0;var d=e[m++];if(a=p.get(d)){var f=a._p;c=!0}else{a=r.createElement("link");a.href=\nd;a.rel="stylesheet";for(a.dataset.precedence=l=e[m++];f=e[m++];)a.setAttribute(f,e[m++]);f=a._p=new Promise(function(n,z){a.onload=v.bind(a,n);a.onerror=v.bind(a,z)});p.set(d,a)}d=a.getAttribute("media");!f||d&&!matchMedia(d).matches||h.push(f);if(c)continue}else{a=x[b++];if(!a)break;l=a.getAttribute("data-precedence");a.removeAttribute("media")}c=q.get(l)||g;c===g&&(g=a);q.set(l,a);c?c.parentNode.insertBefore(a,c.nextSibling):(c=r.head,c.insertBefore(a,c.firstChild))}Promise.all(h).then(w.bind(null,\nt,u,""),w.bind(null,t,u,"Resource failed to load"))};';
10 export const completeSegment =
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js
+10 -7
@@ -3,11 +3,13 @@
3 // Shared implementation and constants between the inline script and external
4 // runtime instruction sets.
5
6 -export const COMMENT_NODE = 8;
7 -export const SUSPENSE_START_DATA = '$';
8 -export const SUSPENSE_END_DATA = '/$';
9 -export const SUSPENSE_PENDING_START_DATA = '$?';
10 -export const SUSPENSE_FALLBACK_START_DATA = '$!';
6 +const COMMENT_NODE = 8;
7 +const ACTIVITY_START_DATA = '&';
8 +const ACTIVITY_END_DATA = '/&';
9 +const SUSPENSE_START_DATA = '$';
10 +const SUSPENSE_END_DATA = '/$';
11 +const SUSPENSE_PENDING_START_DATA = '$?';
12 +const SUSPENSE_FALLBACK_START_DATA = '$!';
13
14 // TODO: Symbols that are referenced outside this module use dynamic accessor
15 // notation instead of dot notation to prevent Closure's advanced compilation
@@ -74,7 +76,7 @@ export function completeBoundary(suspenseBoundaryID, contentID, errorDigest) {
76 do {
77 if (node && node.nodeType === COMMENT_NODE) {
78 const data = node.data;
77 - if (data === SUSPENSE_END_DATA) {
79 + if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {
80 if (depth === 0) {
81 break;
82 } else {
@@ -83,7 +85,8 @@ export function completeBoundary(suspenseBoundaryID, contentID, errorDigest) {
85 } else if (
86 data === SUSPENSE_START_DATA ||
87 data === SUSPENSE_PENDING_START_DATA ||
86 - data === SUSPENSE_FALLBACK_START_DATA
88 + data === SUSPENSE_FALLBACK_START_DATA ||
89 + data === ACTIVITY_START_DATA
90 ) {
91 depth++;
92 }
packages/react-dom/src/client/ReactDOMRoot.js
+2 -2
@@ -47,8 +47,8 @@ export type CreateRootOptions = {
47
48 export type HydrateRootOptions = {
49 // Hydration options
50 - onHydrated?: (suspenseNode: Comment) => void,
51 - onDeleted?: (suspenseNode: Comment) => void,
50 + onHydrated?: (hydrationBoundary: Comment) => void,
51 + onDeleted?: (hydrationBoundary: Comment) => void,
52 // Options for all roots
53 unstable_strictMode?: boolean,
54 unstable_transitionCallbacks?: TransitionTracingCallbacks,
packages/react-reconciler/src/ReactFiberBeginWork.js
+5
@@ -244,6 +244,7 @@ import {
244 claimHydratableSingleton,
245 tryToClaimNextHydratableInstance,
246 tryToClaimNextHydratableTextInstance,
247 + claimNextHydratableActivityInstance,
248 claimNextHydratableSuspenseInstance,
249 warnIfHydrating,
250 queueHydrationError,
@@ -905,6 +906,10 @@ function updateActivityComponent(
906 };
907
908 if (current === null) {
909 + if (getIsHydrating()) {
910 + claimNextHydratableActivityInstance(workInProgress);
911 + }
912 +
913 const primaryChildFragment = mountWorkInProgressOffscreenFiber(
914 offscreenChildProps,
915 mode,
packages/react-reconciler/src/ReactFiberCommitHostEffects.js
+6 -6
@@ -41,10 +41,10 @@ import {
41 insertBefore,
42 insertInContainerBefore,
43 replaceContainerChildren,
44 - hideSuspenseBoundary,
44 + hideDehydratedBoundary,
45 hideInstance,
46 hideTextInstance,
47 - unhideSuspenseBoundary,
47 + unhideDehydratedBoundary,
48 unhideInstance,
49 unhideTextInstance,
50 commitHydratedContainer,
@@ -159,15 +159,15 @@ export function commitShowHideSuspenseBoundary(node: Fiber, isHidden: boolean) {
159 const instance = node.stateNode;
160 if (isHidden) {
161 if (__DEV__) {
162 - runWithFiberInDEV(node, hideSuspenseBoundary, instance);
162 + runWithFiberInDEV(node, hideDehydratedBoundary, instance);
163 } else {
164 - hideSuspenseBoundary(instance);
164 + hideDehydratedBoundary(instance);
165 }
166 } else {
167 if (__DEV__) {
168 - runWithFiberInDEV(node, unhideSuspenseBoundary, node.stateNode);
168 + runWithFiberInDEV(node, unhideDehydratedBoundary, node.stateNode);
169 } else {
170 - unhideSuspenseBoundary(node.stateNode);
170 + unhideDehydratedBoundary(node.stateNode);
171 }
172 }
173 } catch (error) {
packages/react-reconciler/src/ReactFiberCompleteWork.js
+10 -1
@@ -997,7 +997,6 @@ function completeWork(
997 }
998 // Fallthrough
999 }
1000 - case ActivityComponent:
1000 case LazyComponent:
1001 case SimpleMemoComponent:
1002 case FunctionComponent:
@@ -1393,6 +1392,16 @@ function completeWork(
1392 bubbleProperties(workInProgress);
1393 return null;
1394 }
1395 + case ActivityComponent: {
1396 + if (current === null) {
1397 + const wasHydrated = popHydrationState(workInProgress);
1398 + if (wasHydrated) {
1399 + // TODO: Implement prepareToHydrateActivityInstance
1400 + }
1401 + }
1402 + bubbleProperties(workInProgress);
1403 + return null;
1404 + }
1405 case SuspenseComponent: {
1406 const nextState: null | SuspenseState = workInProgress.memoizedState;
1407
packages/react-reconciler/src/ReactFiberConfigWithNoHydration.js
+10 -2
@@ -19,6 +19,7 @@ function shim(...args: any): empty {
19 }
20
21 // Hydration (when unsupported)
22 +export type ActivityInstance = mixed;
23 export type SuspenseInstance = mixed;
24 export const supportsHydration = false;
25 export const isSuspenseInstancePending = shim;
@@ -31,21 +32,28 @@ export const getNextHydratableSibling = shim;
32 export const getNextHydratableSiblingAfterSingleton = shim;
33 export const getFirstHydratableChild = shim;
34 export const getFirstHydratableChildWithinContainer = shim;
35 +export const getFirstHydratableChildWithinActivityInstance = shim;
36 export const getFirstHydratableChildWithinSuspenseInstance = shim;
37 export const getFirstHydratableChildWithinSingleton = shim;
38 export const canHydrateInstance = shim;
39 export const canHydrateTextInstance = shim;
40 +export const canHydrateActivityInstance = shim;
41 export const canHydrateSuspenseInstance = shim;
42 export const hydrateInstance = shim;
43 export const hydrateTextInstance = shim;
44 +export const hydrateActivityInstance = shim;
45 export const hydrateSuspenseInstance = shim;
46 +export const getNextHydratableInstanceAfterActivityInstance = shim;
47 export const getNextHydratableInstanceAfterSuspenseInstance = shim;
48 export const commitHydratedContainer = shim;
49 +export const commitHydratedActivityInstance = shim;
50 export const commitHydratedSuspenseInstance = shim;
51 +export const clearActivityBoundary = shim;
52 export const clearSuspenseBoundary = shim;
53 +export const clearActivityBoundaryFromContainer = shim;
54 export const clearSuspenseBoundaryFromContainer = shim;
47 -export const hideSuspenseBoundary = shim;
48 -export const unhideSuspenseBoundary = shim;
55 +export const hideDehydratedBoundary = shim;
56 +export const unhideDehydratedBoundary = shim;
57 export const shouldDeleteUnhydratedTailInstances = shim;
58 export const diffHydratedPropsForDevWarnings = shim;
59 export const diffHydratedTextForDevWarnings = shim;
packages/react-reconciler/src/ReactFiberHydrationContext.js
+47
@@ -12,6 +12,7 @@ import type {
12 Instance,
13 TextInstance,
14 HydratableInstance,
15 + ActivityInstance,
16 SuspenseInstance,
17 Container,
18 HostContext,
@@ -26,6 +27,7 @@ import {
27 HostSingleton,
28 HostRoot,
29 SuspenseComponent,
30 + ActivityComponent,
31 } from './ReactWorkTags';
32 import {favorSafetyOverHydrationPerf} from 'shared/ReactFeatureFlags';
33
@@ -40,6 +42,7 @@ import {
42 getNextHydratableSiblingAfterSingleton,
43 getFirstHydratableChild,
44 getFirstHydratableChildWithinContainer,
45 + getFirstHydratableChildWithinActivityInstance,
46 getFirstHydratableChildWithinSuspenseInstance,
47 getFirstHydratableChildWithinSingleton,
48 hydrateInstance,
@@ -48,11 +51,13 @@ import {
51 hydrateTextInstance,
52 diffHydratedTextForDevWarnings,
53 hydrateSuspenseInstance,
54 + getNextHydratableInstanceAfterActivityInstance,
55 getNextHydratableInstanceAfterSuspenseInstance,
56 shouldDeleteUnhydratedTailInstances,
57 resolveSingletonInstance,
58 canHydrateInstance,
59 canHydrateTextInstance,
60 + canHydrateActivityInstance,
61 canHydrateSuspenseInstance,
62 canHydrateFormStateMarker,
63 isFormStateMarkerMatching,
@@ -272,6 +277,26 @@ function tryHydrateText(fiber: Fiber, nextInstance: any) {
277 return false;
278 }
279
280 +function tryHydrateActivity(
281 + fiber: Fiber,
282 + nextInstance: any,
283 +): null | ActivityInstance {
284 + // fiber is a SuspenseComponent Fiber
285 + const activityInstance = canHydrateActivityInstance(
286 + nextInstance,
287 + rootOrSingletonContext,
288 + );
289 + if (activityInstance !== null) {
290 + // TODO: Implement dehydrated Activity state.
291 + // TODO: Delete this from stateNode. It's only used to skip past it.
292 + fiber.stateNode = activityInstance;
293 + hydrationParentFiber = fiber;
294 + nextHydratableInstance =
295 + getFirstHydratableChildWithinActivityInstance(activityInstance);
296 + }
297 + return activityInstance;
298 +}
299 +
300 function tryHydrateSuspense(
301 fiber: Fiber,
302 nextInstance: any,
@@ -425,6 +450,18 @@ function tryToClaimNextHydratableTextInstance(fiber: Fiber): void {
450 }
451 }
452
453 +function claimNextHydratableActivityInstance(fiber: Fiber): ActivityInstance {
454 + const nextInstance = nextHydratableInstance;
455 + const activityInstance = nextInstance
456 + ? tryHydrateActivity(fiber, nextInstance)
457 + : null;
458 + if (activityInstance === null) {
459 + warnNonHydratedInstance(fiber, nextInstance);
460 + throw throwOnHydrationMismatch(fiber);
461 + }
462 + return activityInstance;
463 +}
464 +
465 function claimNextHydratableSuspenseInstance(fiber: Fiber): SuspenseInstance {
466 const nextInstance = nextHydratableInstance;
467 const suspenseInstance = nextInstance
@@ -576,6 +613,11 @@ function prepareToHydrateHostSuspenseInstance(fiber: Fiber): void {
613
614 hydrateSuspenseInstance(suspenseInstance, fiber);
615 }
616 +function skipPastDehydratedActivityInstance(
617 + fiber: Fiber,
618 +): null | HydratableInstance {
619 + return getNextHydratableInstanceAfterActivityInstance(fiber.stateNode);
620 +}
621
622 function skipPastDehydratedSuspenseInstance(
623 fiber: Fiber,
@@ -612,6 +654,8 @@ function popToNextHostParent(fiber: Fiber): void {
654 case HostRoot:
655 rootOrSingletonContext = true;
656 return;
657 + case ActivityComponent:
658 + return;
659 default:
660 hydrationParentFiber = hydrationParentFiber.return;
661 }
@@ -677,6 +721,8 @@ function popHydrationState(fiber: Fiber): boolean {
721 popToNextHostParent(fiber);
722 if (tag === SuspenseComponent) {
723 nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber);
724 + } else if (tag === ActivityComponent) {
725 + nextHydratableInstance = skipPastDehydratedActivityInstance(fiber);
726 } else if (supportsSingletons && tag === HostSingleton) {
727 nextHydratableInstance = getNextHydratableSiblingAfterSingleton(
728 fiber.type,
@@ -793,6 +839,7 @@ export {
839 claimHydratableSingleton,
840 tryToClaimNextHydratableInstance,
841 tryToClaimNextHydratableTextInstance,
842 + claimNextHydratableActivityInstance,
843 claimNextHydratableSuspenseInstance,
844 prepareToHydrateHostInstance,
845 prepareToHydrateHostTextInstance,
packages/react-reconciler/src/ReactFiberHydrationDiffs.js
+3
@@ -14,6 +14,7 @@ import {
14 HostHoistable,
15 HostSingleton,
16 LazyComponent,
17 + ActivityComponent,
18 SuspenseComponent,
19 SuspenseListComponent,
20 FunctionComponent,
@@ -83,6 +84,8 @@ function describeFiberType(fiber: Fiber): null | string {
84 return fiber.type;
85 case LazyComponent:
86 return 'Lazy';
87 + case ActivityComponent:
88 + return 'Activity';
89 case SuspenseComponent:
90 return 'Suspense';
91 case SuspenseListComponent:
packages/react-reconciler/src/ReactFiberTreeReflection.js
+13 -1
@@ -8,7 +8,12 @@
8 */
9
10 import type {Fiber} from './ReactInternalTypes';
11 -import type {Container, SuspenseInstance, Instance} from './ReactFiberConfig';
11 +import type {
12 + Container,
13 + ActivityInstance,
14 + SuspenseInstance,
15 + Instance,
16 +} from './ReactFiberConfig';
17 import type {SuspenseState} from './ReactFiberSuspenseComponent';
18
19 import {
@@ -74,6 +79,13 @@ export function getSuspenseInstanceFromFiber(
79 return null;
80 }
81
82 +export function getActivityInstanceFromFiber(
83 + fiber: Fiber,
84 +): null | ActivityInstance {
85 + // TODO: Implement this on ActivityComponent.
86 + return null;
87 +}
88 +
89 export function getContainerFromFiber(fiber: Fiber): null | Container {
90 return fiber.tag === HostRoot
91 ? (fiber.stateNode.containerInfo: Container)
packages/react-reconciler/src/ReactInternalTypes.js
+5 -2
@@ -29,6 +29,7 @@ import type {
29 Instance,
30 TimeoutHandle,
31 NoTimeout,
32 + ActivityInstance,
33 SuspenseInstance,
34 TransitionStatus,
35 } from './ReactFiberConfig';
@@ -297,8 +298,10 @@ type UpdaterTrackingOnlyFiberRootProperties = {
298 };
299
300 export type SuspenseHydrationCallbacks = {
300 - onHydrated?: (suspenseInstance: SuspenseInstance) => void,
301 - onDeleted?: (suspenseInstance: SuspenseInstance) => void,
301 + +onHydrated?: (
302 + hydrationBoundary: SuspenseInstance | ActivityInstance,
303 + ) => void,
304 + +onDeleted?: (hydrationBoundary: SuspenseInstance | ActivityInstance) => void,
305 ...
306 };
307
packages/react-reconciler/src/forks/ReactFiberConfig.custom.js
+14 -2
@@ -29,6 +29,7 @@ export opaque type Props = mixed;
29 export opaque type Container = mixed;
30 export opaque type Instance = mixed;
31 export opaque type TextInstance = mixed;
32 +export opaque type ActivityInstance = mixed;
33 export opaque type SuspenseInstance = mixed;
34 export opaque type HydratableInstance = mixed;
35 export opaque type PublicInstance = mixed;
@@ -202,26 +203,37 @@ export const getNextHydratableSiblingAfterSingleton =
203 export const getFirstHydratableChild = $$$config.getFirstHydratableChild;
204 export const getFirstHydratableChildWithinContainer =
205 $$$config.getFirstHydratableChildWithinContainer;
206 +export const getFirstHydratableChildWithinActivityInstance =
207 + $$$config.getFirstHydratableChildWithinActivityInstance;
208 export const getFirstHydratableChildWithinSuspenseInstance =
209 $$$config.getFirstHydratableChildWithinSuspenseInstance;
210 export const getFirstHydratableChildWithinSingleton =
211 $$$config.getFirstHydratableChildWithinSingleton;
212 export const canHydrateInstance = $$$config.canHydrateInstance;
213 export const canHydrateTextInstance = $$$config.canHydrateTextInstance;
214 +export const canHydrateActivityInstance = $$$config.canHydrateActivityInstance;
215 export const canHydrateSuspenseInstance = $$$config.canHydrateSuspenseInstance;
216 export const hydrateInstance = $$$config.hydrateInstance;
217 export const hydrateTextInstance = $$$config.hydrateTextInstance;
218 +export const hydrateActivityInstance = $$$config.hydrateActivityInstance;
219 export const hydrateSuspenseInstance = $$$config.hydrateSuspenseInstance;
220 +export const getNextHydratableInstanceAfterActivityInstance =
221 + $$$config.getNextHydratableInstanceAfterActivityInstance;
222 export const getNextHydratableInstanceAfterSuspenseInstance =
223 $$$config.getNextHydratableInstanceAfterSuspenseInstance;
224 export const commitHydratedContainer = $$$config.commitHydratedContainer;
225 +export const commitHydratedActivityInstance =
226 + $$$config.commitHydratedActivityInstance;
227 export const commitHydratedSuspenseInstance =
228 $$$config.commitHydratedSuspenseInstance;
229 +export const clearActivityBoundary = $$$config.clearActivityBoundary;
230 export const clearSuspenseBoundary = $$$config.clearSuspenseBoundary;
231 +export const clearActivityBoundaryFromContainer =
232 + $$$config.clearActivityBoundaryFromContainer;
233 export const clearSuspenseBoundaryFromContainer =
234 $$$config.clearSuspenseBoundaryFromContainer;
223 -export const hideSuspenseBoundary = $$$config.hideSuspenseBoundary;
224 -export const unhideSuspenseBoundary = $$$config.unhideSuspenseBoundary;
235 +export const hideDehydratedBoundary = $$$config.hideDehydratedBoundary;
236 +export const unhideDehydratedBoundary = $$$config.unhideDehydratedBoundary;
237 export const shouldDeleteUnhydratedTailInstances =
238 $$$config.shouldDeleteUnhydratedTailInstances;
239 export const diffHydratedPropsForDevWarnings =