Sitemap
Ramu Narasinga

Ramu Narasinga

I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.

startTimingTracking in react-scan source code.

3 min readMar 12, 2025

--

In this article, we review the below code snippet picked from event-tracking.ts

export const startTimingTracking = () => {
const unSubPerformance = setupPerformancePublisher();
const unSubDirtyTaskTracking = startDirtyTaskTracking();
const unSubLongPipelineTracking = startLongPipelineTracking();

const onComplete = async (
_: string,
finalInteraction: FinalInteraction,
event: PerformanceEntryChannelEvent,
) => {
toolbarEventStore.getState().actions.addEvent({
kind: 'interaction',
id: not_globally_unique_generateId(),
data: {
startAt: finalInteraction.detailedTiming.blockingTimeStart,
endAt: performance.now() + performance.timeOrigin,
meta: { ...finalInteraction, kind: event.kind }, // TODO, will need interaction specific metadata here
},
});

const existingCompletedInteractions =
performanceEntryChannels.getChannelState('recording');

finalInteraction.detailedTiming.stopListeningForRenders();

if (existingCompletedInteractions.length) {
// then performance entry and our detailed timing handlers are out of sync, we disregard that entry
// it may be possible the performance entry returned before detailed timing. If that's the case we should update
// assumptions and deal with mapping the entry back to the detailed timing here
performanceEntryChannels.updateChannelState(
'recording',
() => new BoundedArray(MAX_CHANNEL_SIZE),
);
}
};
const unSubDetailedPointerTiming = setupDetailedPointerTimingListener(
'pointer',
{
onComplete,
},
);
const unSubDetailedKeyboardTiming = setupDetailedPointerTimingListener(
'keyboard',
{
onComplete,
},
);

const unSubInteractions = listenForPerformanceEntryInteractions(
(completedInteraction) => {
interactionStore.setState(
BoundedArray.fromArray(
interactionStore.getCurrentState().concat(completedInteraction),
MAX_INTERACTION_BATCH,
),
);
},
);

return () => {
unSubDirtyTaskTracking();
unSubLongPipelineTracking();
unSubPerformance();
unSubDetailedPointerTiming();
unSubInteractions();
unSubDetailedKeyboardTiming();
};
};

Let’s go over this function in chunks.

const unSubPerformance = setupPerformancePublisher();
const unSubDirtyTaskTracking = startDirtyTaskTracking();
const unSubLongPipelineTracking = startLongPipelineTracking();

this calls three functions — setupPerformancePublisher, startDirtyTaskTracking, startLongPipelineTracking.

startDirtyTaskTracking and startLongPipelineTracking are defined in the same event-tracking.ts file except for the setupPerformancePublisher which is imported from core/notifications/performance.ts.

If there is one thing that I noticed, it is the cleanup involved across the react-scan codebase to avoid memory leaks.

Press enter or click to view image in full size

onComplete function:

const onComplete = async (
_: string,
finalInteraction: FinalInteraction,
event: PerformanceEntryChannelEvent,
) => {
toolbarEventStore.getState().actions.addEvent({
kind: 'interaction',
id: not_globally_unique_generateId(),
data: {
startAt: finalInteraction.detailedTiming.blockingTimeStart,
endAt: performance.now() + performance.timeOrigin,
meta: { ...finalInteraction, kind: event.kind }, // TODO, will need interaction specific metadata here
},
});

const existingCompletedInteractions =
performanceEntryChannels.getChannelState('recording');

finalInteraction.detailedTiming.stopListeningForRenders();

if (existingCompletedInteractions.length) {
// then performance entry and our detailed timing handlers are out of sync, we disregard that entry
// it may be possible the performance entry returned before detailed timing. If that's the case we should update
// assumptions and deal with mapping the entry back to the detailed timing here
performanceEntryChannels.updateChannelState(
'recording',
() => new BoundedArray(MAX_CHANNEL_SIZE),
);
}
};

this onComplete arrow function is defined in startTimingTracking. There’s an “interaction” event added and then there is this comment explaining what that if block is about:

// then performance entry and our detailed timing handlers are out of sync, we disregard that entry
// it may be possible the performance entry returned before detailed timing. If that's the case we should update
// assumptions and deal with mapping the entry back to the detailed timing here
const unSubDetailedPointerTiming = setupDetailedPointerTimingListener(
'pointer',
{
onComplete,
},
);
const unSubDetailedKeyboardTiming = setupDetailedPointerTimingListener(
'keyboard',
{
onComplete,
},
);

const unSubInteractions = listenForPerformanceEntryInteractions(
(completedInteraction) => {
interactionStore.setState(
BoundedArray.fromArray(
interactionStore.getCurrentState().concat(completedInteraction),
MAX_INTERACTION_BATCH,
),
);
},
);

These 2 functions — setupDetailedPointerTimingListener and listenForPerformanceEntryInteractions are defined in performance.ts

return () => {
unSubDirtyTaskTracking();
unSubLongPipelineTracking();
unSubPerformance();
unSubDetailedPointerTiming();
unSubInteractions();
unSubDetailedKeyboardTiming();
};

This function in the end returns an arrow function that triggers unsubscription/cleanup.

About me:

Hey, my name is Ramu Narasinga. I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.

Get Ramu Narasinga’s stories in your inbox

Join Medium for free to get updates from this writer.

I am open to work on interesting projects. Send me an email at ramu.narasinga@gmail.com

My Github — https://github.com/ramu-narasinga

My website — https://ramunarasinga.com

My Youtube channel — https://www.youtube.com/@thinkthroo

Learning platform — https://thinkthroo.com

Codebase Architecture — https://app.thinkthroo.com/architecture

Best practices — https://app.thinkthroo.com/best-practices

Production-grade projects — https://app.thinkthroo.com/production-grade-projects

References:

  1. https://github.com/aidenybai/react-scan/blob/main/packages/scan/src/core/notifications/event-tracking.ts#L364
  2. https://github.com/aidenybai/react-scan/blob/main/packages/scan/src/core/notifications/event-tracking.ts#L365
  3. https://github.com/aidenybai/react-scan/blob/main/packages/scan/src/core/notifications/event-tracking.ts#L423
  4. https://github.com/aidenybai/react-scan/blob/main/packages/scan/src/core/notifications/performance.ts#L333
  5. https://github.com/aidenybai/react-scan/blob/main/packages/scan/src/core/notifications/performance.ts#L333

--

--

Ramu Narasinga
Ramu Narasinga

I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.