@@ -405,7 +405,7 @@ export function scheduleUpdateOnFiber(
405
405
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
406
406
// root inside of batchedUpdates should be synchronous, but layout updates
407
407
// should be deferred until the end of the batch.
408
- renderRoot ( root , Sync , true ) ;
408
+ performSyncWorkOnRoot ( root , Sync ) ;
409
409
} else {
410
410
ensureRootIsScheduled ( root ) ;
411
411
schedulePendingInteractions ( root , expirationTime ) ;
@@ -593,16 +593,18 @@ function ensureRootIsScheduled(root: FiberRoot) {
593
593
let callbackNode ;
594
594
if ( expirationTime === Sync ) {
595
595
// Sync React callbacks are scheduled on a special internal queue
596
- callbackNode = scheduleSyncCallback ( performWorkOnRoot . bind ( null , root ) ) ;
596
+ callbackNode = scheduleSyncCallback (
597
+ performSyncWorkOnRoot . bind ( null , root , Sync ) ,
598
+ ) ;
597
599
} else if ( disableSchedulerTimeoutBasedOnReactExpirationTime ) {
598
600
callbackNode = scheduleCallback (
599
601
priorityLevel ,
600
- performWorkOnRoot . bind ( null , root ) ,
602
+ performConcurrentWorkOnRoot . bind ( null , root ) ,
601
603
) ;
602
604
} else {
603
605
callbackNode = scheduleCallback (
604
606
priorityLevel ,
605
- performWorkOnRoot . bind ( null , root ) ,
607
+ performConcurrentWorkOnRoot . bind ( null , root ) ,
606
608
// Compute a task timeout based on the expiration time. This also affects
607
609
// ordering because tasks are processed in timeout order.
608
610
{ timeout : expirationTimeToMs ( expirationTime ) - now ( ) } ,
@@ -612,45 +614,57 @@ function ensureRootIsScheduled(root: FiberRoot) {
612
614
root . callbackNode = callbackNode ;
613
615
}
614
616
615
- // This is the entry point for every concurrent task.
616
- function performWorkOnRoot ( root , isSync ) {
617
+ // This is the entry point for every concurrent task, i.e. anything that
618
+ // goes through Scheduler.
619
+ function performConcurrentWorkOnRoot ( root , didTimeout ) {
620
+ // Since we know we're in a React event, we can clear the current
621
+ // event time. The next update will compute a new event time.
622
+ currentEventTime = NoWork ;
623
+
617
624
// Determine the next expiration time to work on, using the fields stored
618
625
// on the root.
619
626
let expirationTime = getNextRootExpirationTimeToWorkOn ( root ) ;
620
627
if ( expirationTime !== NoWork ) {
621
- if ( expirationTime !== Sync ) {
622
- // Since we know we're in a React event, we can clear the current
623
- // event time. The next update will compute a new event time.
624
- currentEventTime = NoWork ;
625
-
628
+ if ( didTimeout ) {
626
629
// An async update expired. There may be other expired updates on
627
630
// this root.
628
- if ( isSync ) {
629
- const currentTime = requestCurrentTime ( ) ;
630
- if ( currentTime < expirationTime ) {
631
- // Render all the expired work in a single batch.
632
- expirationTime = currentTime ;
633
- }
631
+ const currentTime = requestCurrentTime ( ) ;
632
+ if ( currentTime < expirationTime ) {
633
+ // Render all the expired work in a single batch.
634
+ expirationTime = currentTime ;
634
635
}
635
636
}
636
637
637
638
const originalCallbackNode = root . callbackNode ;
638
639
try {
639
- renderRoot ( root , expirationTime , isSync ) ;
640
- } finally {
641
- // Before exiting, make sure there's a callback scheduled for the
642
- // pending level.
643
- ensureRootIsScheduled ( root ) ;
640
+ renderRoot ( root , expirationTime , didTimeout ) ;
644
641
if ( root . callbackNode === originalCallbackNode ) {
645
642
// The task node scheduled for this root is the same one that's
646
643
// currently executed. Need to return a continuation.
647
- return performWorkOnRoot . bind ( null , root ) ;
644
+ return performConcurrentWorkOnRoot . bind ( null , root ) ;
648
645
}
646
+ } finally {
647
+ // Before exiting, make sure there's a callback scheduled for the
648
+ // pending level.
649
+ ensureRootIsScheduled ( root ) ;
649
650
}
650
651
}
651
652
return null ;
652
653
}
653
654
655
+ // This is the entry point for synchronous tasks that don't go
656
+ // through Scheduler
657
+ function performSyncWorkOnRoot ( root , expirationTime ) {
658
+ try {
659
+ renderRoot ( root , expirationTime , true ) ;
660
+ } finally {
661
+ // Before exiting, make sure there's a callback scheduled for the
662
+ // pending level.
663
+ ensureRootIsScheduled ( root ) ;
664
+ }
665
+ return null ;
666
+ }
667
+
654
668
export function flushRoot ( root : FiberRoot , expirationTime : ExpirationTime ) {
655
669
if ( ( executionContext & ( RenderContext | CommitContext ) ) !== NoContext ) {
656
670
invariant (
@@ -659,11 +673,7 @@ export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
659
673
'means you attempted to commit from inside a lifecycle method.' ,
660
674
) ;
661
675
}
662
- scheduleSyncCallback ( ( ) => {
663
- renderRoot ( root , expirationTime , true ) ;
664
- return null ;
665
- } ) ;
666
- flushSyncCallbackQueue ( ) ;
676
+ performSyncWorkOnRoot ( root , expirationTime ) ;
667
677
}
668
678
669
679
export function flushDiscreteUpdates ( ) {
@@ -731,10 +741,9 @@ function flushPendingDiscreteUpdates() {
731
741
const roots = rootsWithPendingDiscreteUpdates ;
732
742
rootsWithPendingDiscreteUpdates = null ;
733
743
roots . forEach ( ( expirationTime , root ) => {
734
- scheduleSyncCallback ( ( ) => {
735
- renderRoot ( root , expirationTime , true ) ;
736
- return null ;
737
- } ) ;
744
+ scheduleSyncCallback (
745
+ performSyncWorkOnRoot . bind ( null , root , expirationTime ) ,
746
+ ) ;
738
747
} ) ;
739
748
// Now flush the immediate queue.
740
749
flushSyncCallbackQueue ( ) ;
@@ -879,6 +888,8 @@ function prepareFreshStack(root, expirationTime) {
879
888
}
880
889
}
881
890
891
+ // renderRoot should only be called from inside either
892
+ // `performConcurrentWorkOnRoot` or `performSyncWorkOnRoot`.
882
893
function renderRoot (
883
894
root : FiberRoot ,
884
895
expirationTime : ExpirationTime ,
@@ -1021,7 +1032,7 @@ function renderRoot(
1021
1032
// synchronously, to see if the error goes away. If there are lower
1022
1033
// priority updates, let's include those, too, in case they fix the
1023
1034
// inconsistency. Render at Idle to include all updates.
1024
- renderRoot ( root , Idle , true ) ;
1035
+ performSyncWorkOnRoot ( root , Idle ) ;
1025
1036
return ;
1026
1037
}
1027
1038
// Commit the root in its errored state.
@@ -1863,9 +1874,8 @@ function commitRootImpl(root, renderPriorityLevel) {
1863
1874
) ;
1864
1875
}
1865
1876
}
1877
+ schedulePendingInteractions ( root , remainingExpirationTime ) ;
1866
1878
}
1867
- ensureRootIsScheduled ( root ) ;
1868
- schedulePendingInteractions ( root , expirationTime) ;
1869
1879
} else {
1870
1880
// If there's no remaining work, we can clear the set of already failed
1871
1881
// error boundaries.
@@ -1882,8 +1892,6 @@ function commitRootImpl(root, renderPriorityLevel) {
1882
1892
}
1883
1893
}
1884
1894
1885
- onCommitRoot ( finishedWork . stateNode , expirationTime ) ;
1886
-
1887
1895
if ( remainingExpirationTime === Sync ) {
1888
1896
// Count the number of times the root synchronously re-renders without
1889
1897
// finishing. If there are too many, it indicates an infinite update loop.
@@ -1897,6 +1905,12 @@ function commitRootImpl(root, renderPriorityLevel) {
1897
1905
nestedUpdateCount = 0 ;
1898
1906
}
1899
1907
1908
+ onCommitRoot ( finishedWork . stateNode , expirationTime ) ;
1909
+
1910
+ // Always call this before exiting `commitRoot`, to ensure that any
1911
+ // additional work on this root is scheduled.
1912
+ ensureRootIsScheduled ( root ) ;
1913
+
1900
1914
if ( hasUncaughtError ) {
1901
1915
hasUncaughtError = false ;
1902
1916
const error = firstUncaughtError ;
0 commit comments