Skip to content

Commit 0c813c5

Browse files
authored
[Tracks]: display method name and component name for updates in DEV (#34463)
For every "Update" entry we are going to add properties that will be displayed when the user clicks on that entry: name of the method that caused this first update and name of the component where this update happened. We could use the name of the component as a deeplink to React DevTools components panel in the future, once we support stable identificators on Fibers. <img width="1444" height="530" alt="Screenshot 2025-09-10 at 18 31 10" src="https://github.com/user-attachments/assets/7f9af037-2e7f-4e7b-9b7e-bf9f7d5a6e72" /> <img width="2088" height="530" alt="Screenshot 2025-09-10 at 18 24 21" src="https://github.com/user-attachments/assets/f557a173-bd9b-43f7-9333-74066f433ced" /> <img width="2088" height="530" alt="Screenshot 2025-09-10 at 18 26 04" src="https://github.com/user-attachments/assets/ff37d13f-bbe3-4f85-800e-81aa3aed7833" />
1 parent a9ad64c commit 0c813c5

File tree

6 files changed

+115
-59
lines changed

6 files changed

+115
-59
lines changed

packages/react-reconciler/src/ReactFiberClassComponent.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ const classComponentUpdater = {
179179

180180
const root = enqueueUpdate(fiber, update, lane);
181181
if (root !== null) {
182-
startUpdateTimerByLane(lane, 'this.setState()');
182+
startUpdateTimerByLane(lane, 'this.setState()', fiber);
183183
scheduleUpdateOnFiber(root, fiber, lane);
184184
entangleTransitions(root, fiber, lane);
185185
}
@@ -205,7 +205,7 @@ const classComponentUpdater = {
205205

206206
const root = enqueueUpdate(fiber, update, lane);
207207
if (root !== null) {
208-
startUpdateTimerByLane(lane, 'this.replaceState()');
208+
startUpdateTimerByLane(lane, 'this.replaceState()', fiber);
209209
scheduleUpdateOnFiber(root, fiber, lane);
210210
entangleTransitions(root, fiber, lane);
211211
}
@@ -231,7 +231,7 @@ const classComponentUpdater = {
231231

232232
const root = enqueueUpdate(fiber, update, lane);
233233
if (root !== null) {
234-
startUpdateTimerByLane(lane, 'this.forceUpdate()');
234+
startUpdateTimerByLane(lane, 'this.forceUpdate()', fiber);
235235
scheduleUpdateOnFiber(root, fiber, lane);
236236
entangleTransitions(root, fiber, lane);
237237
}

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,7 +1866,7 @@ function subscribeToStore<T>(
18661866
// read from the store.
18671867
if (checkIfSnapshotChanged(inst)) {
18681868
// Force a re-render.
1869-
startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()');
1869+
startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()', fiber);
18701870
forceStoreRerender(fiber);
18711871
}
18721872
};
@@ -3518,7 +3518,7 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T): void {
35183518
const refreshUpdate = createLegacyQueueUpdate(lane);
35193519
const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane);
35203520
if (root !== null) {
3521-
startUpdateTimerByLane(lane, 'refresh()');
3521+
startUpdateTimerByLane(lane, 'refresh()', fiber);
35223522
scheduleUpdateOnFiber(root, provider, lane);
35233523
entangleLegacyQueueTransitions(root, provider, lane);
35243524
}
@@ -3587,7 +3587,7 @@ function dispatchReducerAction<S, A>(
35873587
} else {
35883588
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
35893589
if (root !== null) {
3590-
startUpdateTimerByLane(lane, 'dispatch()');
3590+
startUpdateTimerByLane(lane, 'dispatch()', fiber);
35913591
scheduleUpdateOnFiber(root, fiber, lane);
35923592
entangleTransitionUpdate(root, queue, lane);
35933593
}
@@ -3621,7 +3621,7 @@ function dispatchSetState<S, A>(
36213621
lane,
36223622
);
36233623
if (didScheduleUpdate) {
3624-
startUpdateTimerByLane(lane, 'setState()');
3624+
startUpdateTimerByLane(lane, 'setState()', fiber);
36253625
}
36263626
markUpdateInDevTools(fiber, lane, action);
36273627
}
@@ -3783,7 +3783,7 @@ function dispatchOptimisticSetState<S, A>(
37833783
// will never be attempted before the optimistic update. This currently
37843784
// holds because the optimistic update is always synchronous. If we ever
37853785
// change that, we'll need to account for this.
3786-
startUpdateTimerByLane(lane, 'setOptimistic()');
3786+
startUpdateTimerByLane(lane, 'setOptimistic()', fiber);
37873787
scheduleUpdateOnFiber(root, fiber, lane);
37883788
// Optimistic updates are always synchronous, so we don't need to call
37893789
// entangleTransitionUpdate here.

packages/react-reconciler/src/ReactFiberPerformanceTrack.js

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,8 @@ export function logBlockingStart(
631631
renderStartTime: number,
632632
lanes: Lanes,
633633
debugTask: null | ConsoleTask, // DEV-only
634+
updateMethodName: null | string,
635+
updateComponentName: null | string,
634636
): void {
635637
if (supportsUserTiming) {
636638
currentTrack = 'Blocking';
@@ -672,34 +674,46 @@ export function logBlockingStart(
672674
: includesOnlyHydrationOrOffscreenLanes(lanes)
673675
? 'tertiary-light'
674676
: 'primary-light';
675-
if (__DEV__ && debugTask) {
676-
debugTask.run(
677-
// $FlowFixMe[method-unbinding]
678-
console.timeStamp.bind(
679-
console,
680-
isPingedUpdate
681-
? 'Promise Resolved'
682-
: isSpawnedUpdate
683-
? 'Cascading Update'
684-
: renderStartTime - updateTime > 5
685-
? 'Update Blocked'
686-
: 'Update',
687-
updateTime,
688-
renderStartTime,
689-
currentTrack,
690-
LANES_TRACK_GROUP,
691-
color,
692-
),
693-
);
677+
const label = isPingedUpdate
678+
? 'Promise Resolved'
679+
: isSpawnedUpdate
680+
? 'Cascading Update'
681+
: renderStartTime - updateTime > 5
682+
? 'Update Blocked'
683+
: 'Update';
684+
685+
if (__DEV__) {
686+
const properties = [];
687+
if (updateComponentName != null) {
688+
properties.push(['Component name', updateComponentName]);
689+
}
690+
if (updateMethodName != null) {
691+
properties.push(['Method name', updateMethodName]);
692+
}
693+
const measureOptions = {
694+
start: updateTime,
695+
end: renderStartTime,
696+
detail: {
697+
devtools: {
698+
properties,
699+
track: currentTrack,
700+
trackGroup: LANES_TRACK_GROUP,
701+
color,
702+
},
703+
},
704+
};
705+
706+
if (debugTask) {
707+
debugTask.run(
708+
// $FlowFixMe[method-unbinding]
709+
performance.measure.bind(performance, label, measureOptions),
710+
);
711+
} else {
712+
performance.measure(label, measureOptions);
713+
}
694714
} else {
695715
console.timeStamp(
696-
isPingedUpdate
697-
? 'Promise Resolved'
698-
: isSpawnedUpdate
699-
? 'Cascading Update'
700-
: renderStartTime - updateTime > 5
701-
? 'Update Blocked'
702-
: 'Update',
716+
label,
703717
updateTime,
704718
renderStartTime,
705719
currentTrack,
@@ -720,6 +734,8 @@ export function logTransitionStart(
720734
isPingedUpdate: boolean,
721735
renderStartTime: number,
722736
debugTask: null | ConsoleTask, // DEV-only
737+
updateMethodName: null | string,
738+
updateComponentName: null | string,
723739
): void {
724740
if (supportsUserTiming) {
725741
currentTrack = 'Transition';
@@ -781,30 +797,43 @@ export function logTransitionStart(
781797
}
782798
if (updateTime > 0 && renderStartTime > updateTime) {
783799
// Log the time from when we called setState until we started rendering.
784-
if (__DEV__ && debugTask) {
785-
debugTask.run(
786-
// $FlowFixMe[method-unbinding]
787-
console.timeStamp.bind(
788-
console,
789-
isPingedUpdate
790-
? 'Promise Resolved'
791-
: renderStartTime - updateTime > 5
792-
? 'Update Blocked'
793-
: 'Update',
794-
updateTime,
795-
renderStartTime,
796-
currentTrack,
797-
LANES_TRACK_GROUP,
798-
'primary-light',
799-
),
800-
);
800+
const label = isPingedUpdate
801+
? 'Promise Resolved'
802+
: renderStartTime - updateTime > 5
803+
? 'Update Blocked'
804+
: 'Update';
805+
if (__DEV__) {
806+
const properties = [];
807+
if (updateComponentName != null) {
808+
properties.push(['Component name', updateComponentName]);
809+
}
810+
if (updateMethodName != null) {
811+
properties.push(['Method name', updateMethodName]);
812+
}
813+
const measureOptions = {
814+
start: updateTime,
815+
end: renderStartTime,
816+
detail: {
817+
devtools: {
818+
properties,
819+
track: currentTrack,
820+
trackGroup: LANES_TRACK_GROUP,
821+
color: 'primary-light',
822+
},
823+
},
824+
};
825+
826+
if (debugTask) {
827+
debugTask.run(
828+
// $FlowFixMe[method-unbinding]
829+
performance.measure.bind(performance, label, measureOptions),
830+
);
831+
} else {
832+
performance.measure(label, measureOptions);
833+
}
801834
} else {
802835
console.timeStamp(
803-
isPingedUpdate
804-
? 'Promise Resolved'
805-
: renderStartTime - updateTime > 5
806-
? 'Update Blocked'
807-
: 'Update',
836+
label,
808837
updateTime,
809838
renderStartTime,
810839
currentTrack,

packages/react-reconciler/src/ReactFiberReconciler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ export function createHydrationContainer(
346346
update.callback =
347347
callback !== undefined && callback !== null ? callback : null;
348348
enqueueUpdate(current, update, lane);
349-
startUpdateTimerByLane(lane, 'hydrateRoot()');
349+
startUpdateTimerByLane(lane, 'hydrateRoot()', null);
350350
scheduleInitialHydrationOnRoot(root, lane);
351351

352352
return root;
@@ -453,7 +453,7 @@ function updateContainerImpl(
453453

454454
const root = enqueueUpdate(rootFiber, update, lane);
455455
if (root !== null) {
456-
startUpdateTimerByLane(lane, 'root.render()');
456+
startUpdateTimerByLane(lane, 'root.render()', null);
457457
scheduleUpdateOnFiber(root, rootFiber, lane);
458458
entangleTransitions(root, rootFiber, lane);
459459
}

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ import {
267267
blockingUpdateTime,
268268
blockingUpdateTask,
269269
blockingUpdateType,
270+
blockingUpdateMethodName,
271+
blockingUpdateComponentName,
270272
blockingEventTime,
271273
blockingEventType,
272274
blockingEventIsRepeat,
@@ -276,6 +278,8 @@ import {
276278
transitionUpdateTime,
277279
transitionUpdateTask,
278280
transitionUpdateType,
281+
transitionUpdateMethodName,
282+
transitionUpdateComponentName,
279283
transitionEventTime,
280284
transitionEventType,
281285
transitionEventIsRepeat,
@@ -1940,6 +1944,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
19401944
renderStartTime,
19411945
lanes,
19421946
blockingUpdateTask,
1947+
blockingUpdateMethodName,
1948+
blockingUpdateComponentName,
19431949
);
19441950
clearBlockingTimers();
19451951
}
@@ -1980,6 +1986,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
19801986
transitionUpdateType === PINGED_UPDATE,
19811987
renderStartTime,
19821988
transitionUpdateTask,
1989+
transitionUpdateMethodName,
1990+
transitionUpdateComponentName,
19831991
);
19841992
clearTransitionTimers();
19851993
}

packages/react-reconciler/src/ReactProfilerTimer.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
enableComponentPerformanceTrack,
3434
} from 'shared/ReactFeatureFlags';
3535

36+
import getComponentNameFromFiber from './getComponentNameFromFiber';
3637
import {isAlreadyRendering} from './ReactFiberWorkLoop';
3738

3839
// Intentionally not named imports because Rollup would use dynamic dispatch for
@@ -68,6 +69,8 @@ export let blockingClampTime: number = -0;
6869
export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
6970
export let blockingUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace.
7071
export let blockingUpdateType: UpdateType = 0;
72+
export let blockingUpdateMethodName: null | string = null; // The name of the method that caused first sync update.
73+
export let blockingUpdateComponentName: null | string = null; // The name of the component where first sync update happened.
7174
export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
7275
export let blockingEventType: null | string = null; // Event type of the first setState.
7376
export let blockingEventIsRepeat: boolean = false;
@@ -78,6 +81,8 @@ export let transitionStartTime: number = -1.1; // First startTransition call bef
7881
export let transitionUpdateTime: number = -1.1; // First transition setState scheduled.
7982
export let transitionUpdateType: UpdateType = 0;
8083
export let transitionUpdateTask: null | ConsoleTask = null; // First transition setState's stack trace.
84+
export let transitionUpdateMethodName: null | string = null; // The name of the method that caused first transition update.
85+
export let transitionUpdateComponentName: null | string = null; // The name of the component where first transition update happened.
8186
export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
8287
export let transitionEventType: null | string = null; // Event type of the first transition.
8388
export let transitionEventIsRepeat: boolean = false;
@@ -94,14 +99,22 @@ export function startYieldTimer(reason: SuspendedReason) {
9499
yieldReason = reason;
95100
}
96101

97-
export function startUpdateTimerByLane(lane: Lane, method: string): void {
102+
export function startUpdateTimerByLane(
103+
lane: Lane,
104+
method: string,
105+
fiber: Fiber | null,
106+
): void {
98107
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
99108
return;
100109
}
101110
if (isSyncLane(lane) || isBlockingLane(lane)) {
102111
if (blockingUpdateTime < 0) {
103112
blockingUpdateTime = now();
104113
blockingUpdateTask = createTask(method);
114+
blockingUpdateMethodName = method;
115+
if (__DEV__ && fiber != null) {
116+
blockingUpdateComponentName = getComponentNameFromFiber(fiber);
117+
}
105118
if (isAlreadyRendering()) {
106119
blockingUpdateType = SPAWNED_UPDATE;
107120
}
@@ -125,6 +138,10 @@ export function startUpdateTimerByLane(lane: Lane, method: string): void {
125138
if (transitionUpdateTime < 0) {
126139
transitionUpdateTime = now();
127140
transitionUpdateTask = createTask(method);
141+
transitionUpdateMethodName = method;
142+
if (__DEV__ && fiber != null) {
143+
transitionUpdateComponentName = getComponentNameFromFiber(fiber);
144+
}
128145
if (transitionStartTime < 0) {
129146
const newEventTime = resolveEventTimeStamp();
130147
const newEventType = resolveEventType();
@@ -225,6 +242,8 @@ export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) {
225242
export function clearBlockingTimers(): void {
226243
blockingUpdateTime = -1.1;
227244
blockingUpdateType = 0;
245+
blockingUpdateMethodName = null;
246+
blockingUpdateComponentName = null;
228247
blockingSuspendedTime = -1.1;
229248
blockingEventIsRepeat = true;
230249
}

0 commit comments

Comments
 (0)