[Fiber] Track SuspendedState on stack instead of global (#34486)
Stacked on #34481. We currently track the suspended state temporarily with a global which is safe as long as we always read it during a sync pass. However, we sometimes read it in closures and then we have to be carefully to pass the right one since it's possible another commit on a different root has started at that point. This avoids this footgun. Another reason to do this is that I want to read it in `startViewTransition` which is in an async gap after which point it's no longer safe. So I have to pass that through the `commitRoot` bound function.
Sebastian Markbåge committed
Sep 15, 2025 at 16:10 UTC
5d49b2b7f42b6aebe749914fbd06640ded63c001
10 files changed
+165
-88
packages/react-art/src/ReactFiberConfigART.js
+5
-3
@@ -609,11 +609,13 @@ export function preloadInstance(type, props) {
609
return true;
610
}
611
612
-export function startSuspendingCommit() {}
612
+export function startSuspendingCommit() {
613
+ return null;
614
+}
615
614
-export function suspendInstance(instance, type, props) {}
616
+export function suspendInstance(state, instance, type, props) {}
617
616
-export function suspendOnActiveViewTransition(container) {}
618
+export function suspendOnActiveViewTransition(state, container) {}
619
620
export function waitForCommitToBeReady(timeoutOffset) {
621
return null;
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+12
-31
@@ -2081,6 +2081,7 @@ function forceLayout(ownerDocument: Document) {
2081
}
2082
2083
export function startViewTransition(
2084
+ suspendedState: null | SuspendedState,
2085
rootContainer: Container,
2086
transitionTypes: null | TransitionTypes,
2087
mutationCallback: () => void,
@@ -2443,6 +2444,7 @@ function animateGesture(
2444
}
2445
2446
export function startGestureTransition(
2447
+ suspendedState: null | SuspendedState,
2448
rootContainer: Container,
2449
timeline: GestureTimeline,
2450
rangeStart: number,
@@ -5904,7 +5906,7 @@ export function preloadResource(resource: Resource): boolean {
5906
return true;
5907
}
5908
5907
-type SuspendedState = {
5909
+export opaque type SuspendedState = {
5910
stylesheets: null | Map<StylesheetResource, HoistableRoot>,
5911
count: number, // suspensey css and active view transitions
5912
imgCount: number, // suspensey images
@@ -5912,10 +5914,9 @@ type SuspendedState = {
5914
waitingForImages: boolean, // false when we're no longer blocking on images
5915
unsuspend: null | (() => void),
5916
};
5915
-let suspendedState: null | SuspendedState = null;
5917
5917
-export function startSuspendingCommit(): void {
5918
- suspendedState = {
5918
+export function startSuspendingCommit(): SuspendedState {
5919
+ return {
5920
stylesheets: null,
5921
count: 0,
5922
imgCount: 0,
@@ -5930,6 +5931,7 @@ export function startSuspendingCommit(): void {
5931
}
5932
5933
export function suspendInstance(
5934
+ state: SuspendedState,
5935
instance: Instance,
5936
type: Type,
5937
props: Props,
@@ -5937,12 +5939,6 @@ export function suspendInstance(
5939
if (!enableSuspenseyImages && !enableViewTransition) {
5940
return;
5941
}
5940
- if (suspendedState === null) {
5941
- throw new Error(
5942
- 'Internal React Error: suspendedState null when it was expected to exists. Please report this as a React bug.',
5943
- );
5944
- }
5945
- const state = suspendedState;
5942
if (
5943
// $FlowFixMe[prop-missing]
5944
typeof instance.decode === 'function' &&
@@ -5971,16 +5967,11 @@ export function suspendInstance(
5967
}
5968
5969
export function suspendResource(
5970
+ state: SuspendedState,
5971
hoistableRoot: HoistableRoot,
5972
resource: Resource,
5973
props: any,
5974
): void {
5978
- if (suspendedState === null) {
5979
- throw new Error(
5980
- 'Internal React Error: suspendedState null when it was expected to exists. Please report this as a React bug.',
5981
- );
5982
- }
5983
- const state = suspendedState;
5975
if (resource.type === 'stylesheet') {
5976
if (typeof props.media === 'string') {
5977
// If we don't currently match media we avoid suspending on this resource
@@ -6060,13 +6051,10 @@ export function suspendResource(
6051
}
6052
}
6053
6063
-export function suspendOnActiveViewTransition(rootContainer: Container): void {
6064
- if (suspendedState === null) {
6065
- throw new Error(
6066
- 'Internal React Error: suspendedState null when it was expected to exists. Please report this as a React bug.',
6067
- );
6068
- }
6069
- const state = suspendedState;
6054
+export function suspendOnActiveViewTransition(
6055
+ state: SuspendedState,
6056
+ rootContainer: Container,
6057
+): void {
6058
const ownerDocument =
6059
rootContainer.nodeType === DOCUMENT_NODE
6060
? rootContainer
@@ -6090,16 +6078,9 @@ const SUSPENSEY_IMAGE_TIME_ESTIMATE = 500;
6078
let estimatedBytesWithinLimit: number = 0;
6079
6080
export function waitForCommitToBeReady(
6081
+ state: SuspendedState,
6082
timeoutOffset: number,
6083
): null | ((() => void) => () => void) {
6095
- if (suspendedState === null) {
6096
- throw new Error(
6097
- 'Internal React Error: suspendedState null when it was expected to exists. Please report this as a React bug.',
6098
- );
6099
- }
6100
-
6101
- const state = suspendedState;
6102
-
6084
if (state.stylesheets && state.count === 0) {
6085
// We are not currently blocked but we have not inserted all stylesheets.
6086
// If this insertion happens and loads or errors synchronously then we can
packages/react-native-renderer/src/ReactFiberConfigFabric.js
+14
-3
@@ -602,17 +602,28 @@ export function preloadInstance(
602
return true;
603
}
604
605
-export function startSuspendingCommit(): void {}
605
+export opaque type SuspendedState = null;
606
+
607
+export function startSuspendingCommit(): SuspendedState {
608
+ return null;
609
+}
610
611
export function suspendInstance(
612
+ state: SuspendedState,
613
instance: Instance,
614
type: Type,
615
props: Props,
616
): void {}
617
613
-export function suspendOnActiveViewTransition(container: Container): void {}
618
+export function suspendOnActiveViewTransition(
619
+ state: SuspendedState,
620
+ container: Container,
621
+): void {}
622
615
-export function waitForCommitToBeReady(timeoutOffset: number): null {
623
+export function waitForCommitToBeReady(
624
+ state: SuspendedState,
625
+ timeoutOffset: number,
626
+): null {
627
return null;
628
}
629
packages/react-native-renderer/src/ReactFiberConfigNative.js
+16
-3
@@ -664,6 +664,7 @@ export function hasInstanceAffectedParent(
664
}
665
666
export function startViewTransition(
667
+ suspendedState: null | SuspendedState,
668
rootContainer: Container,
669
transitionTypes: null | TransitionTypes,
670
mutationCallback: () => void,
@@ -684,6 +685,7 @@ export function startViewTransition(
685
export type RunningViewTransition = null;
686
687
export function startGestureTransition(
688
+ suspendedState: null | SuspendedState,
689
rootContainer: Container,
690
timeline: GestureTimeline,
691
rangeStart: number,
@@ -778,17 +780,28 @@ export function preloadInstance(
780
return true;
781
}
782
781
-export function startSuspendingCommit(): void {}
783
+export opaque type SuspendedState = null;
784
+
785
+export function startSuspendingCommit(): SuspendedState {
786
+ return null;
787
+}
788
789
export function suspendInstance(
790
+ state: SuspendedState,
791
instance: Instance,
792
type: Type,
793
props: Props,
794
): void {}
795
789
-export function suspendOnActiveViewTransition(container: Container): void {}
796
+export function suspendOnActiveViewTransition(
797
+ state: SuspendedState,
798
+ container: Container,
799
+): void {}
800
791
-export function waitForCommitToBeReady(timeoutOffset: number): null {
801
+export function waitForCommitToBeReady(
802
+ state: SuspendedState,
803
+ timeoutOffset: number,
804
+): null {
805
return null;
806
}
807
packages/react-noop-renderer/src/createReactNoop.js
+21
-24
@@ -90,6 +90,8 @@ type SuspenseyCommitSubscription = {
90
commit: null | (() => void),
91
};
92
93
+export opaque type SuspendedState = SuspenseyCommitSubscription;
94
+
95
export type TransitionStatus = mixed;
96
97
export type FormInstance = Instance;
@@ -311,17 +313,17 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
313
'pending' | 'fulfilled',
314
> | null = null;
315
314
- // Represents a subscription for all the suspensey things that block a
315
- // particular commit. Once they've all loaded, the commit phase can proceed.
316
- let suspenseyCommitSubscription: SuspenseyCommitSubscription | null = null;
317
-
318
- function startSuspendingCommit(): void {
319
- // This is where we might suspend on things that aren't associated with a
320
- // particular node, like document.fonts.ready.
321
- suspenseyCommitSubscription = null;
316
+ function startSuspendingCommit(): SuspendedState {
317
+ // Represents a subscription for all the suspensey things that block a
318
+ // particular commit. Once they've all loaded, the commit phase can proceed.
319
+ return {
320
+ pendingCount: 0,
321
+ commit: null,
322
+ };
323
}
324
325
function suspendInstance(
326
+ state: SuspendedState,
327
instance: Instance,
328
type: string,
329
props: Props,
@@ -338,20 +340,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
340
if (record.status === 'fulfilled') {
341
// Already loaded.
342
} else if (record.status === 'pending') {
341
- if (suspenseyCommitSubscription === null) {
342
- suspenseyCommitSubscription = {
343
- pendingCount: 1,
344
- commit: null,
345
- };
346
- } else {
347
- suspenseyCommitSubscription.pendingCount++;
348
- }
343
+ state.pendingCount++;
344
// Stash the subscription on the record. In `resolveSuspenseyThing`,
345
// we'll use this fire the commit once all the things have loaded.
346
if (record.subscriptions === null) {
347
record.subscriptions = [];
348
}
354
- record.subscriptions.push(suspenseyCommitSubscription);
349
+ record.subscriptions.push(state);
350
}
351
} else {
352
throw new Error(
@@ -362,15 +357,14 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
357
}
358
359
function waitForCommitToBeReady(
360
+ state: SuspendedState,
361
timeoutOffset: number,
362
): ((commit: () => mixed) => () => void) | null {
367
- const subscription = suspenseyCommitSubscription;
368
- if (subscription !== null) {
369
- suspenseyCommitSubscription = null;
363
+ if (state.pendingCount > 0) {
364
return (commit: () => void) => {
371
- subscription.commit = commit;
365
+ state.commit = commit;
366
const cancelCommit = () => {
373
- subscription.commit = null;
367
+ state.commit = null;
368
};
369
return cancelCommit;
370
};
@@ -693,13 +687,16 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
687
startSuspendingCommit,
688
suspendInstance,
689
696
- suspendResource(resource: mixed): void {
690
+ suspendResource(state: SuspendedState, resource: mixed): void {
691
throw new Error(
692
'Resources are not implemented for React Noop yet. This method should not be called',
693
);
694
},
695
702
- suspendOnActiveViewTransition(container: Container): void {
696
+ suspendOnActiveViewTransition(
697
+ state: SuspendedState,
698
+ container: Container,
699
+ ): void {
700
// Not implemented
701
},
702
packages/react-reconciler/src/ReactFiberCommitWork.js
+57
-13
@@ -16,6 +16,7 @@ import type {
16
HoistableRoot,
17
FormInstance,
18
Props,
19
+ SuspendedState,
20
} from './ReactFiberConfig';
21
import type {Fiber, FiberRoot} from './ReactInternalTypes';
22
import type {Lanes} from './ReactFiberLane';
@@ -4495,31 +4496,46 @@ let suspenseyCommitFlag: Flags = ShouldSuspendCommit;
4496
export function accumulateSuspenseyCommit(
4497
finishedWork: Fiber,
4498
committedLanes: Lanes,
4499
+ suspendedState: SuspendedState,
4500
): void {
4501
resetAppearingViewTransitions();
4500
- accumulateSuspenseyCommitOnFiber(finishedWork, committedLanes);
4502
+ accumulateSuspenseyCommitOnFiber(
4503
+ finishedWork,
4504
+ committedLanes,
4505
+ suspendedState,
4506
+ );
4507
}
4508
4509
function recursivelyAccumulateSuspenseyCommit(
4510
parentFiber: Fiber,
4511
committedLanes: Lanes,
4512
+ suspendedState: SuspendedState,
4513
): void {
4514
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
4515
let child = parentFiber.child;
4516
while (child !== null) {
4510
- accumulateSuspenseyCommitOnFiber(child, committedLanes);
4517
+ accumulateSuspenseyCommitOnFiber(child, committedLanes, suspendedState);
4518
child = child.sibling;
4519
}
4520
}
4521
}
4522
4516
-function accumulateSuspenseyCommitOnFiber(fiber: Fiber, committedLanes: Lanes) {
4523
+function accumulateSuspenseyCommitOnFiber(
4524
+ fiber: Fiber,
4525
+ committedLanes: Lanes,
4526
+ suspendedState: SuspendedState,
4527
+) {
4528
switch (fiber.tag) {
4529
case HostHoistable: {
4519
- recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
4530
+ recursivelyAccumulateSuspenseyCommit(
4531
+ fiber,
4532
+ committedLanes,
4533
+ suspendedState,
4534
+ );
4535
if (fiber.flags & suspenseyCommitFlag) {
4536
if (fiber.memoizedState !== null) {
4537
suspendResource(
4538
+ suspendedState,
4539
// This should always be set by visiting HostRoot first
4540
(currentHoistableRoot: any),
4541
fiber.memoizedState,
@@ -4534,14 +4550,18 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber, committedLanes: Lanes) {
4550
includesOnlySuspenseyCommitEligibleLanes(committedLanes) ||
4551
maySuspendCommitInSyncRender(type, props)
4552
) {
4537
- suspendInstance(instance, type, props);
4553
+ suspendInstance(suspendedState, instance, type, props);
4554
}
4555
}
4556
}
4557
break;
4558
}
4559
case HostComponent: {
4544
- recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
4560
+ recursivelyAccumulateSuspenseyCommit(
4561
+ fiber,
4562
+ committedLanes,
4563
+ suspendedState,
4564
+ );
4565
if (fiber.flags & suspenseyCommitFlag) {
4566
const instance = fiber.stateNode;
4567
const type = fiber.type;
@@ -4551,7 +4571,7 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber, committedLanes: Lanes) {
4571
includesOnlySuspenseyCommitEligibleLanes(committedLanes) ||
4572
maySuspendCommitInSyncRender(type, props)
4573
) {
4554
- suspendInstance(instance, type, props);
4574
+ suspendInstance(suspendedState, instance, type, props);
4575
}
4576
}
4577
break;
@@ -4563,10 +4583,18 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber, committedLanes: Lanes) {
4583
const container: Container = fiber.stateNode.containerInfo;
4584
currentHoistableRoot = getHoistableRoot(container);
4585
4566
- recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
4586
+ recursivelyAccumulateSuspenseyCommit(
4587
+ fiber,
4588
+ committedLanes,
4589
+ suspendedState,
4590
+ );
4591
currentHoistableRoot = previousHoistableRoot;
4592
} else {
4569
- recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
4593
+ recursivelyAccumulateSuspenseyCommit(
4594
+ fiber,
4595
+ committedLanes,
4596
+ suspendedState,
4597
+ );
4598
}
4599
break;
4600
}
@@ -4584,10 +4612,18 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber, committedLanes: Lanes) {
4612
// instances, even if they're in the current tree.
4613
const prevFlags = suspenseyCommitFlag;
4614
suspenseyCommitFlag = MaySuspendCommit;
4587
- recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
4615
+ recursivelyAccumulateSuspenseyCommit(
4616
+ fiber,
4617
+ committedLanes,
4618
+ suspendedState,
4619
+ );
4620
suspenseyCommitFlag = prevFlags;
4621
} else {
4590
- recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
4622
+ recursivelyAccumulateSuspenseyCommit(
4623
+ fiber,
4624
+ committedLanes,
4625
+ suspendedState,
4626
+ );
4627
}
4628
}
4629
break;
@@ -4607,13 +4643,21 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber, committedLanes: Lanes) {
4643
trackAppearingViewTransition(name, state);
4644
}
4645
}
4610
- recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
4646
+ recursivelyAccumulateSuspenseyCommit(
4647
+ fiber,
4648
+ committedLanes,
4649
+ suspendedState,
4650
+ );
4651
break;
4652
}
4653
// Fallthrough
4654
}
4655
default: {
4616
- recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
4656
+ recursivelyAccumulateSuspenseyCommit(
4657
+ fiber,
4658
+ committedLanes,
4659
+ suspendedState,
4660
+ );
4661
}
4662
}
4663
}
packages/react-reconciler/src/ReactFiberWorkLoop.js
+17
-4
@@ -26,6 +26,7 @@ import type {
26
Resource,
27
ViewTransitionInstance,
28
RunningViewTransition,
29
+ SuspendedState,
30
} from './ReactFiberConfig';
31
import type {RootState} from './ReactFiberRoot';
32
import {
@@ -1379,6 +1380,7 @@ function finishConcurrentRender(
1380
workInProgressRootInterleavedUpdatedLanes,
1381
workInProgressSuspendedRetryLanes,
1382
exitStatus,
1383
+ null,
1384
IMMEDIATE_COMMIT,
1385
renderStartTime,
1386
renderEndTime,
@@ -1487,22 +1489,23 @@ function commitRootWhenReady(
1489
subtreeFlags & ShouldSuspendCommit ||
1490
(subtreeFlags & BothVisibilityAndMaySuspendCommit) ===
1491
BothVisibilityAndMaySuspendCommit;
1492
+ let suspendedState: null | SuspendedState = null;
1493
if (isViewTransitionEligible || maySuspendCommit || isGestureTransition) {
1494
// Before committing, ask the renderer whether the host tree is ready.
1495
// If it's not, we'll wait until it notifies us.
1493
- startSuspendingCommit();
1496
+ suspendedState = startSuspendingCommit();
1497
// This will walk the completed fiber tree and attach listeners to all
1498
// the suspensey resources. The renderer is responsible for accumulating
1499
// all the load events. This all happens in a single synchronous
1500
// transaction, so it track state in its own module scope.
1501
// This will also track any newly added or appearing ViewTransition
1502
// components for the purposes of forming pairs.
1500
- accumulateSuspenseyCommit(finishedWork, lanes);
1503
+ accumulateSuspenseyCommit(finishedWork, lanes, suspendedState);
1504
if (isViewTransitionEligible || isGestureTransition) {
1505
// If we're stopping gestures we don't have to wait for any pending
1506
// view transition. We'll stop it when we commit.
1507
if (!enableGestureTransition || root.stoppingGestures === null) {
1505
- suspendOnActiveViewTransition(root.containerInfo);
1508
+ suspendOnActiveViewTransition(suspendedState, root.containerInfo);
1509
}
1510
}
1511
// For timeouts we use the previous fallback commit for retries and
@@ -1516,7 +1519,10 @@ function commitRootWhenReady(
1519
// At the end, ask the renderer if it's ready to commit, or if we should
1520
// suspend. If it's not ready, it will return a callback to subscribe to
1521
// a ready event.
1519
- const schedulePendingCommit = waitForCommitToBeReady(timeoutOffset);
1522
+ const schedulePendingCommit = waitForCommitToBeReady(
1523
+ suspendedState,
1524
+ timeoutOffset,
1525
+ );
1526
if (schedulePendingCommit !== null) {
1527
// NOTE: waitForCommitToBeReady returns a subscribe function so that we
1528
// only allocate a function if the commit isn't ready yet. The other
@@ -1538,6 +1544,7 @@ function commitRootWhenReady(
1544
updatedLanes,
1545
suspendedRetryLanes,
1546
exitStatus,
1547
+ suspendedState,
1548
SUSPENDED_COMMIT,
1549
completedRenderStartTime,
1550
completedRenderEndTime,
@@ -1561,6 +1568,7 @@ function commitRootWhenReady(
1568
updatedLanes,
1569
suspendedRetryLanes,
1570
exitStatus,
1571
+ suspendedState,
1572
suspendedCommitReason,
1573
completedRenderStartTime,
1574
completedRenderEndTime,
@@ -3284,6 +3292,7 @@ function commitRoot(
3292
updatedLanes: Lanes,
3293
suspendedRetryLanes: Lanes,
3294
exitStatus: RootExitStatus,
3295
+ suspendedState: null | SuspendedState,
3296
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
3297
completedRenderStartTime: number, // Profiling-only
3298
completedRenderEndTime: number, // Profiling-only
@@ -3435,6 +3444,7 @@ function commitRoot(
3444
root,
3445
finishedWork,
3446
recoverableErrors,
3447
+ suspendedState,
3448
enableProfilerTimer
3449
? suspendedCommitReason === IMMEDIATE_COMMIT
3450
? completedRenderEndTime
@@ -3573,6 +3583,7 @@ function commitRoot(
3583
pendingEffectsStatus = PENDING_MUTATION_PHASE;
3584
if (enableViewTransition && willStartViewTransition) {
3585
pendingViewTransition = startViewTransition(
3586
+ suspendedState,
3587
root.containerInfo,
3588
pendingTransitionTypes,
3589
flushMutationEffects,
@@ -3987,6 +3998,7 @@ function commitGestureOnRoot(
3998
root: FiberRoot,
3999
finishedWork: Fiber,
4000
recoverableErrors: null | Array<CapturedValue<mixed>>,
4001
+ suspendedState: null | SuspendedState,
4002
renderEndTime: number, // Profiling-only
4003
): void {
4004
// We assume that the gesture we just rendered was the first one in the queue.
@@ -4017,6 +4029,7 @@ function commitGestureOnRoot(
4029
pendingEffectsStatus = PENDING_GESTURE_MUTATION_PHASE;
4030
4031
pendingViewTransition = finishedGesture.running = startGestureTransition(
4032
+ suspendedState,
4033
root.containerInfo,
4034
finishedGesture.provider,
4035
finishedGesture.rangeStart,
packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
+6
-4
@@ -106,10 +106,12 @@ describe('ReactFiberHostContext', () => {
106
preloadInstance(instance, type, props) {
107
return true;
108
},
109
- startSuspendingCommit() {},
110
- suspendInstance(instance, type, props) {},
111
- suspendOnActiveViewTransition(container) {},
112
- waitForCommitToBeReady(timeoutOffset: number) {
109
+ startSuspendingCommit() {
110
+ return null;
111
+ },
112
+ suspendInstance(state, instance, type, props) {},
113
+ suspendOnActiveViewTransition(state, container) {},
114
+ waitForCommitToBeReady(state, timeoutOffset) {
115
return null;
116
},
117
supportsMutation: true,
packages/react-reconciler/src/forks/ReactFiberConfig.custom.js
+1
@@ -41,6 +41,7 @@ export opaque type NoTimeout = mixed;
41
export opaque type RendererInspectionConfig = mixed;
42
export opaque type TransitionStatus = mixed;
43
export opaque type FormInstance = mixed;
44
+export opaque type SuspendedState = mixed;
45
export type RunningViewTransition = mixed;
46
export type ViewTransitionInstance = null | {name: string, ...};
47
export opaque type InstanceMeasurement = mixed;
packages/react-test-renderer/src/ReactFiberConfigTestHost.js
+16
-3
@@ -414,6 +414,7 @@ export function hasInstanceAffectedParent(
414
}
415
416
export function startViewTransition(
417
+ suspendedState: null | SuspendedState,
418
rootContainer: Container,
419
transitionTypes: null | TransitionTypes,
420
mutationCallback: () => void,
@@ -434,6 +435,7 @@ export function startViewTransition(
435
export type RunningViewTransition = null;
436
437
export function startGestureTransition(
438
+ suspendedState: null | SuspendedState,
439
rootContainer: Container,
440
timeline: GestureTimeline,
441
rangeStart: number,
@@ -561,17 +563,28 @@ export function preloadInstance(
563
return true;
564
}
565
564
-export function startSuspendingCommit(): void {}
566
+export opaque type SuspendedState = null;
567
+
568
+export function startSuspendingCommit(): SuspendedState {
569
+ return null;
570
+}
571
572
export function suspendInstance(
573
+ state: SuspendedState,
574
instance: Instance,
575
type: Type,
576
props: Props,
577
): void {}
578
572
-export function suspendOnActiveViewTransition(container: Container): void {}
579
+export function suspendOnActiveViewTransition(
580
+ state: SuspendedState,
581
+ container: Container,
582
+): void {}
583
574
-export function waitForCommitToBeReady(timeoutOffset: number): null {
584
+export function waitForCommitToBeReady(
585
+ state: SuspendedState,
586
+ timeoutOffset: number,
587
+): null {
588
return null;
589
}
590