diff --git a/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js b/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js index e458f8060f84b..ba23ac3782077 100644 --- a/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js +++ b/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js @@ -1581,6 +1581,143 @@ describe('preprocessData', () => { ); } }); + + it('should not warn about transition updates scheduled during commit phase', async () => { + function Component() { + const [value, setValue] = React.useState(0); + // eslint-disable-next-line no-unused-vars + const [isPending, startTransition] = React.useTransition(); + + Scheduler.unstable_yieldValue( + `Component rendered with value ${value}`, + ); + + // Fake a long render + if (value !== 0) { + Scheduler.unstable_yieldValue('Long render'); + startTime += 20000; + } + + React.useLayoutEffect(() => { + startTransition(() => { + setValue(1); + }); + }, []); + + return value; + } + + if (gate(flags => flags.enableSchedulingProfiler)) { + const cpuProfilerSample = creactCpuProfilerSample(); + + const root = ReactDOM.createRoot(document.createElement('div')); + act(() => { + root.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'Component rendered with value 0', + 'Component rendered with value 0', + 'Component rendered with value 1', + 'Long render', + ]); + + const testMarks = []; + clearedMarks.forEach(markName => { + if (markName === '--component-render-start-Component') { + // Fake a long running render + startTime += 20000; + } + + testMarks.push({ + pid: ++pid, + tid: ++tid, + ts: ++startTime, + args: {data: {}}, + cat: 'blink.user_timing', + name: markName, + ph: 'R', + }); + }); + + const data = await preprocessData([ + cpuProfilerSample, + ...createBoilerplateEntries(), + ...testMarks, + ]); + + data.schedulingEvents.forEach(event => { + expect(event.warning).toBeNull(); + }); + } + }); + + it('should not warn about deferred value updates scheduled during commit phase', async () => { + function Component() { + const [value, setValue] = React.useState(0); + const deferredValue = React.useDeferredValue(value); + + Scheduler.unstable_yieldValue( + `Component rendered with value ${value} and deferredValue ${deferredValue}`, + ); + + // Fake a long render + if (deferredValue !== 0) { + Scheduler.unstable_yieldValue('Long render'); + startTime += 20000; + } + + React.useLayoutEffect(() => { + setValue(1); + }, []); + + return value + deferredValue; + } + + if (gate(flags => flags.enableSchedulingProfiler)) { + const cpuProfilerSample = creactCpuProfilerSample(); + + const root = ReactDOM.createRoot(document.createElement('div')); + act(() => { + root.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'Component rendered with value 0 and deferredValue 0', + 'Component rendered with value 1 and deferredValue 0', + 'Component rendered with value 1 and deferredValue 1', + 'Long render', + ]); + + const testMarks = []; + clearedMarks.forEach(markName => { + if (markName === '--component-render-start-Component') { + // Fake a long running render + startTime += 20000; + } + + testMarks.push({ + pid: ++pid, + tid: ++tid, + ts: ++startTime, + args: {data: {}}, + cat: 'blink.user_timing', + name: markName, + ph: 'R', + }); + }); + + const data = await preprocessData([ + cpuProfilerSample, + ...createBoilerplateEntries(), + ...testMarks, + ]); + + data.schedulingEvents.forEach(event => { + expect(event.warning).toBeNull(); + }); + } + }); }); describe('errors thrown while rendering', () => { diff --git a/packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js b/packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js index aff78142b81a6..6b4233d25390d 100644 --- a/packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js +++ b/packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js @@ -970,7 +970,16 @@ export default async function preprocessData( // See how long the subsequent batch of React work was. const [startTime, stopTime] = getBatchRange(batchUID, profilerData); if (stopTime - startTime > NESTED_UPDATE_DURATION_THRESHOLD) { - schedulingEvent.warning = WARNING_STRINGS.NESTED_UPDATE; + // Don't warn about transition updates scheduled during the commit phase. + // e.g. useTransition, useDeferredValue + // These are allowed to be long-running. + if ( + !schedulingEvent.lanes.some( + lane => profilerData.laneToLabelMap.get(lane) === 'Transition', + ) + ) { + schedulingEvent.warning = WARNING_STRINGS.NESTED_UPDATE; + } } }); state.potentialSuspenseEventsOutsideOfTransition.forEach(