@@ -795,7 +795,8 @@ function prepareFreshStack(root, expirationTime) {
795795
796796 if ( __DEV__ ) {
797797 ReactStrictModeWarnings . discardPendingWarnings ( ) ;
798- componentsWithSuspendedDiscreteUpdates = null ;
798+ componentsThatSuspendedAtHighPri = null ;
799+ componentsThatTriggeredHighPriSuspend = null ;
799800 }
800801}
801802
@@ -982,6 +983,8 @@ function renderRoot(
982983 // Set this to null to indicate there's no in-progress render.
983984 workInProgressRoot = null ;
984985
986+ flushSuspensePriorityWarningInDEV ( ) ;
987+
985988 switch ( workInProgressRootExitStatus ) {
986989 case RootIncomplete : {
987990 invariant ( false , 'Should have a work-in-progress.' ) ;
@@ -1491,7 +1494,6 @@ function commitRoot(root) {
14911494function commitRootImpl ( root ) {
14921495 flushPassiveEffects ( ) ;
14931496 flushRenderPhaseStrictModeWarningsInDEV ( ) ;
1494- flushSuspensePriorityWarningInDEV ( ) ;
14951497
14961498 invariant (
14971499 ( executionContext & ( RenderContext | CommitContext ) ) === NoContext ,
@@ -2520,58 +2522,133 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
25202522
25212523export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV ;
25222524
2523- let componentsWithSuspendedDiscreteUpdates = null ;
2525+ let componentsThatSuspendedAtHighPri = null ;
2526+ let componentsThatTriggeredHighPriSuspend = null ;
25242527export function checkForWrongSuspensePriorityInDEV ( sourceFiber : Fiber ) {
25252528 if ( __DEV__ ) {
2529+ const currentPriorityLevel = getCurrentPriorityLevel ( ) ;
25262530 if (
25272531 ( sourceFiber . mode & ConcurrentMode ) !== NoEffect &&
2528- // Check if we're currently rendering a discrete update. Ideally, all we
2529- // would need to do is check the current priority level. But we currently
2530- // have no rigorous way to distinguish work that was scheduled at user-
2531- // blocking priority from work that expired a bit and was "upgraded" to
2532- // a higher priority. That's because we don't schedule separate callbacks
2533- // for every level, only the highest priority level per root. The priority
2534- // of subsequent levels is inferred from the expiration time, but this is
2535- // an imprecise heuristic.
2536- //
2537- // However, we do store the last discrete pending update per root. So we
2538- // can reliably compare to that one. (If we broaden this warning to include
2539- // high pri updates that aren't discrete, then this won't be sufficient.)
2540- //
2541- // My rationale is that it's better for this warning to have false
2542- // negatives than false positives.
2543- rootsWithPendingDiscreteUpdates !== null &&
2544- workInProgressRoot !== null &&
2545- renderExpirationTime ===
2546- rootsWithPendingDiscreteUpdates . get ( workInProgressRoot )
2532+ ( currentPriorityLevel === UserBlockingPriority ||
2533+ currentPriorityLevel === ImmediatePriority )
25472534 ) {
2535+ let workInProgressNode = sourceFiber ;
2536+ while ( workInProgressNode !== null ) {
2537+ // Add the component that triggered the suspense
2538+ const current = workInProgressNode . alternate ;
2539+ if ( current !== null ) {
2540+ // TODO: warn component that triggers the high priority
2541+ // suspend is the HostRoot
2542+ switch ( workInProgressNode . tag ) {
2543+ case ClassComponent :
2544+ // Loop through the component's update queue and see whether the component
2545+ // has triggered any high priority updates
2546+ const updateQueue = current . updateQueue ;
2547+ if ( updateQueue !== null ) {
2548+ let update = updateQueue . firstUpdate ;
2549+ while ( update !== null ) {
2550+ const priorityLevel = update . priority ;
2551+ if (
2552+ priorityLevel === UserBlockingPriority ||
2553+ priorityLevel === ImmediatePriority
2554+ ) {
2555+ if ( componentsThatTriggeredHighPriSuspend === null ) {
2556+ componentsThatTriggeredHighPriSuspend = new Set ( [
2557+ getComponentName ( workInProgressNode . type ) ,
2558+ ] ) ;
2559+ } else {
2560+ componentsThatTriggeredHighPriSuspend . add (
2561+ getComponentName ( workInProgressNode . type ) ,
2562+ ) ;
2563+ }
2564+ break ;
2565+ }
2566+ update = update . next ;
2567+ }
2568+ }
2569+ break ;
2570+ case FunctionComponent :
2571+ case ForwardRef :
2572+ case SimpleMemoComponent :
2573+ if (
2574+ workInProgressNode . memoizedState !== null &&
2575+ workInProgressNode . memoizedState . baseUpdate !== null
2576+ ) {
2577+ let update = workInProgressNode . memoizedState . baseUpdate ;
2578+ // Loop through the functional component's memoized state to see whether
2579+ // the component has triggered any high pri updates
2580+ while ( update !== null ) {
2581+ const priority = update . priority ;
2582+ if (
2583+ priority === UserBlockingPriority ||
2584+ priority === ImmediatePriority
2585+ ) {
2586+ if ( componentsThatTriggeredHighPriSuspend === null ) {
2587+ componentsThatTriggeredHighPriSuspend = new Set ( [
2588+ getComponentName ( workInProgressNode . type ) ,
2589+ ] ) ;
2590+ } else {
2591+ componentsThatTriggeredHighPriSuspend . add (
2592+ getComponentName ( workInProgressNode . type ) ,
2593+ ) ;
2594+ }
2595+ break ;
2596+ }
2597+ if (
2598+ update . next === workInProgressNode . memoizedState . baseUpdate
2599+ ) {
2600+ break ;
2601+ }
2602+ update = update . next ;
2603+ }
2604+ }
2605+ break ;
2606+ default :
2607+ break ;
2608+ }
2609+ }
2610+ workInProgressNode = workInProgressNode . return ;
2611+ }
2612+
25482613 // Add the component name to a set.
25492614 const componentName = getComponentName ( sourceFiber . type ) ;
2550- if ( componentsWithSuspendedDiscreteUpdates === null ) {
2551- componentsWithSuspendedDiscreteUpdates = new Set ( [ componentName ] ) ;
2615+ if ( componentsThatSuspendedAtHighPri === null ) {
2616+ componentsThatSuspendedAtHighPri = new Set ( [ componentName ] ) ;
25522617 } else {
2553- componentsWithSuspendedDiscreteUpdates . add ( componentName ) ;
2618+ componentsThatSuspendedAtHighPri . add ( componentName ) ;
25542619 }
25552620 }
25562621 }
25572622}
25582623
25592624function flushSuspensePriorityWarningInDEV ( ) {
25602625 if ( __DEV__ ) {
2561- if ( componentsWithSuspendedDiscreteUpdates !== null ) {
2626+ if ( componentsThatSuspendedAtHighPri !== null ) {
25622627 const componentNames = [ ] ;
2563- componentsWithSuspendedDiscreteUpdates . forEach ( name => {
2628+ componentsThatSuspendedAtHighPri . forEach ( name => {
25642629 componentNames . push ( name ) ;
25652630 } ) ;
2566- componentsWithSuspendedDiscreteUpdates = null ;
2631+ componentsThatSuspendedAtHighPri = null ;
2632+
2633+ const componentsThatTriggeredSuspendNames = [ ] ;
2634+ if ( componentsThatTriggeredHighPriSuspend !== null ) {
2635+ componentsThatTriggeredHighPriSuspend . forEach ( name =>
2636+ componentsThatTriggeredSuspendNames . push ( name ) ,
2637+ ) ;
2638+ }
2639+
2640+ componentsThatTriggeredHighPriSuspend = null ;
25672641
2568- // TODO: A more helpful version of this message could include the names of
2569- // the component that were updated, not the ones that suspended. To do
2570- // that we'd need to track all the components that updated during this
2571- // render, perhaps using the same mechanism as `markRenderEventTime`.
2642+ const componentThatTriggeredSuspenseError =
2643+ componentsThatTriggeredSuspendNames . length > 0
2644+ ? '\n' +
2645+ 'The components that triggered the update: ' +
2646+ componentsThatTriggeredSuspendNames . sort ( ) . join ( ', ' )
2647+ : '' ;
25722648 warningWithoutStack (
25732649 false ,
25742650 'The following components suspended during a user-blocking update: %s' +
2651+ '%s' +
25752652 '\n\n' +
25762653 'Updates triggered by user interactions (e.g. click events) are ' +
25772654 'considered user-blocking by default. They should not suspend. ' +
@@ -2585,6 +2662,7 @@ function flushSuspensePriorityWarningInDEV() {
25852662 'feedback, and another update to perform the actual change.' ,
25862663 // TODO: Add link to React docs with more information, once it exists
25872664 componentNames . sort ( ) . join ( ', ' ) ,
2665+ componentThatTriggeredSuspenseError ,
25882666 ) ;
25892667 }
25902668 }
0 commit comments