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 =