698
699
if (__DEV__) {
700
ReactStrictModeWarnings.discardPendingWarnings();
701
+ componentsWithSuspendedDiscreteUpdates = null;
702
}
703
}
704
1226
function commitRootImpl(root, expirationTime) {
1227
flushPassiveEffects();
1228
flushRenderPhaseStrictModeWarningsInDEV();
1229
+ flushSuspensePriorityWarningInDEV();
1230
1231
invariant(
1232
workPhase !== RenderPhase && workPhase !== CommitPhase,
2116
2117
export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
2118
2119
+let componentsWithSuspendedDiscreteUpdates = null;
2120
+export function checkForWrongSuspensePriorityInDEV(sourceFiber: Fiber) {
2121
+ if (__DEV__) {
2122
+ if (
2123
+ (sourceFiber.mode & ConcurrentMode) !== NoEffect &&
2124
+ // Check if we're currently rendering a discrete update. Ideally, all we
2125
+ // would need to do is check the current priority level. But we currently
2126
+ // have no rigorous way to distinguish work that was scheduled at user-
2127
+ // blocking priority from work that expired a bit and was "upgraded" to
2128
+ // a higher priority. That's because we don't schedule separate callbacks
2129
+ // for every level, only the highest priority level per root. The priority
2130
+ // of subsequent levels is inferred from the expiration time, but this is
2131
+ // an imprecise heuristic.
2132
+ //
2133
+ // However, we do store the last discrete pending update per root. So we
2134
+ // can reliably compare to that one. (If we broaden this warning to include
2135
+ // high pri updates that aren't discrete, then this won't be sufficient.)
2136
+ //
2137
+ // My rationale is that it's better for this warning to have false
2138
+ // negatives than false positives.
2139
+ rootsWithPendingDiscreteUpdates !== null &&
2140
+ workInProgressRoot !== null &&
2141
+ renderExpirationTime ===
2142
+ rootsWithPendingDiscreteUpdates.get(workInProgressRoot)
2143
+ ) {
2144
+ // Add the component name to a set.
2145
+ const componentName = getComponentName(sourceFiber.type);
2146
+ if (componentsWithSuspendedDiscreteUpdates === null) {
2147
+ componentsWithSuspendedDiscreteUpdates = new Set([componentName]);
2148
+ } else {
2149
+ componentsWithSuspendedDiscreteUpdates.add(componentName);
2150
+ }
2151
+ }
2152
+ }
2153
+}
2154
+
2155
+function flushSuspensePriorityWarningInDEV() {
2156
+ if (__DEV__) {
2157
+ if (componentsWithSuspendedDiscreteUpdates !== null) {
2158
+ const componentNames = [];
2159
+ componentsWithSuspendedDiscreteUpdates.forEach(name => {
2160
+ componentNames.push(name);
2161
+ });
2162
+ componentsWithSuspendedDiscreteUpdates = null;
2163
+
2164
+ // TODO: A more helpful version of this message could include the names of
2165
+ // the component that were updated, not the ones that suspended. To do
2166
+ // that we'd need to track all the components that updated during this
2167
+ // render, perhaps using the same mechanism as `markRenderEventTime`.
2168
+ warningWithoutStack(
2169
+ false,
2170
+ 'The following components suspended during a user-blocking update: %s' +
2171
+ '\n\n' +
2172
+ 'Updates triggered by user interactions (e.g. click events) are ' +
2173
+ 'considered user-blocking by default. They should not suspend. ' +
2174
+ 'Updates that can afford to take a bit longer should be wrapped ' +
2175
+ 'with `Scheduler.next` (or an equivalent abstraction). This ' +
2176
+ 'typically includes any update that shows new content, like ' +
2177
+ 'a navigation.' +
2178
+ '\n\n' +
2179
+ 'Generally, you should split user interactions into at least two ' +
2180
+ 'seprate updates: a user-blocking update to provide immediate ' +
2181
+ 'feedback, and another update to perform the actual change.',
2182
+ // TODO: Add link to React docs with more information, once it exists
2183
+ componentNames.sort().join(', '),
2184
+ );
2185
+ }
2186
+ }
2187
+}
2188
+
2189
function computeThreadID(root, expirationTime) {
2190
// Interaction threads are unique per root and expiration time.
2191
return expirationTime * 1000 + root.interactionThreadID;