Navigation Instrumentation
Generates: Spans
Overview
Navigation instrumentation tracks following three metrics for every screen in your React Native app, giving you a complete picture of the user experience.
Metrics
- Screen Load - How fast the screen appears after navigation starts
- Screen Interactive - When your meaningful content is actually ready (via your custom callback)
- Screen Session - How long users spend on each screen
Timeline Flow
When a user navigates to a screen, three metrics track different parts of the journey:
User Taps ───────────► Navigation End ───────────► Content Ready ───────────► Navigate Away
│ │ │ │
├──────── Screen Load ────┘ │ │
├──── Screen Interactive ────┘ │
| |
├──────────────────── Screen Session ────────────────────┘
Important: All tracking only works when Navigation instrumentation is enabled (default: enabled) and you've set up the navigation container. See Setup for details.
Setup
Requirements
- React Navigation v5.x or higher
- Supported navigators:
- Stack Navigator - JavaScript-based stack navigation
- Native Stack Navigator - Native platform navigation
Installation
Navigation instrumentation is enabled by default, but you need to connect it to your navigation container. Add the Pulse.useNavigationTracking() hook to your app:
import React from 'react';
import { NavigationContainer, type NavigationContainerRef } from '@react-navigation/native';
import { Pulse } from '@horizoneng/pulse-react-native';
function App() {
// 1. Create a ref for your NavigationContainer
const navigationRef = React.useRef<NavigationContainerRef>(null);
// 2. Get the onReady callback from Pulse
const onReady = Pulse.useNavigationTracking(navigationRef);
return (
// 3. Connect the ref and onReady callback
<NavigationContainer ref={navigationRef} onReady={onReady}>
{/* Your navigation screens */}
</NavigationContainer>
);
}
That's it! Once connected, Screen Load and Screen Session will start tracking automatically. For Screen Interactive tracking, you'll need to enable it first by setting screenInteractiveTracking: true in the configuration, then call Pulse.markContentReady() when your meaningful content is ready. See the Screen Interactive Tracking section for details.
Why This Setup is Needed
The SDK needs a reference to your NavigationContainer to listen for navigation events. Without this connection, navigation instrumentation is enabled but won't track any screens because it can't detect navigation changes.
Configuration
You can disable navigation tracking globally or configure each metric individually.
Disable All Navigation Tracking
To disable all navigation tracking:
Pulse.start({
autoDetectNavigation: false // Default: true
});
This disables all three metrics (Screen Load, Screen Interactive, and Screen Session).
Configure Individual Metrics
Control each metric independently when setting up the hook:
const onReady = Pulse.useNavigationTracking(navigationRef, {
screenSessionTracking: true, // Screen Session metric
screenNavigationTracking: true, // Screen Load metric
screenInteractiveTracking: false, // Screen Interactive metric
});
Default values:
screenSessionTracking:truescreenNavigationTracking:truescreenInteractiveTracking:false
Screen Load Tracking
Measures how quickly a screen appears after a user navigates to it. This tracks the time from when the user taps a button or initiates navigation until the navigation end.
Generated Telemetry:
- Type: Span
- Span Name:
Navigated - Span Kind: INTERNAL
How It Works
The span starts automatically when navigation begins and ends when the screen navigation ends:
- Starts: When user initiates navigation (
navigation.navigate(), programmatic navigation, etc.) - Ends: When the screen navigation ends
- Duration: Time from navigation start to navigation end
Enabled by default - no code needed. Controlled by screenNavigationTracking configuration option.
Attributes
Every Screen Load span includes these attributes:
| Attribute | Description | Example |
|---|---|---|
pulse.type | "screen_load" | "screen_load" |
screen.name | Current screen name | "DetailsHome" |
last.screen.name | Previous screen name | "DetailsInfo" (only if exists) |
routeHasBeenSeen | Whether user visited this screen before | true, false |
routeKey | Unique route identifier | "DetailsHome-Oht5epi34smXv9IlxNggQ" |
phase | Navigation phase | "start" |
Example Payloads
{
"name": "Navigated",
"kind": "INTERNAL",
"duration": "14.44ms",
"attributes": {
"pulse.type": "screen_load",
"screen.name": "DetailsInfo",
"last.screen.name": "DetailsHome",
"routeHasBeenSeen": false,
"routeKey": "DetailsInfo-z46JYmBeWWnsPCJU4H7A8",
"phase": "start"
}
}
Screen Interactive Tracking
Screen Interactive tracking measures when your meaningful content is actually ready for users, not just when the screen navigation ends. While Screen Load tracks screen transition time, Screen Interactive tracks when your content is truly usable - when data has loaded, images have rendered, or any other meaningful content is available.
This gives you precise control over what "ready" means for each screen by calling Pulse.markContentReady() when your content is actually usable. This helps you understand the real user experience - tracking when users can actually interact with your content, not just when the screen transition ends.
Generated Telemetry:
Type: Span
Span Name: ScreenInteractive
Span Kind: INTERNAL
How It Works
The span starts automatically when Screen Navigation ends and ends when you call Pulse.markContentReady(). You control what "ready" means.
- Starts: Automatically when Screen Navigation ends
- Ends: When you call
Pulse.markContentReady() - Duration: Time from screen display to when your content is ready
Opt-in feature - must be enabled. If the user navigates away before you call markContentReady(), the span is discarded.
Configuration
Screen interactive tracking is opt-in and must be explicitly enabled:
const onReady = Pulse.useNavigationTracking(navigationRef, {
screenInteractiveTracking: true, // Opt-in, default: false
});
Using Pulse.markContentReady()
import { useEffect, useState } from 'react';
import { Pulse } from '@horizoneng/pulse-react-native';
function ProfileScreen() {
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadData = async () => {
// Load your data, images, etc.
await fetchUserData();
await loadImages();
setLoading(false);
// Call this when API data is actually loaded
Pulse.markContentReady();
};
loadData();
}, []);
return <ProfileContent />;
}
Lifecycle
Span Start:
- Start Time (T0): When
screen_loadspan ends (screen has finished loading) - Triggered: Automatically after
screen_loadends - Condition:
screenInteractiveTracking: true
Span End:
- End Time (T1): When you call
Pulse.markContentReady()in your code - Triggered: Your custom callback - you decide when meaningful content is ready
- Duration: T1 - T0 = Time from screen load to when your content is actually usable
Note: If the user navigates away before you call markContentReady(), the span is discarded and not sent. This ensures you only track screens where you've explicitly marked content as ready.
Screen Interactive Attributes
| Attribute | Description | Example | Always Present |
|---|---|---|---|
pulse.type | Instrumentation type | "screen_interactive" | Yes |
screen.name | Current screen name | "Profile", "Home" | Yes |
routeKey | Unique route identifier | "Profile-Oht5epi34smXv9IlxNggQ" | Yes |
Sample Screen Interactive Payload
{
"name": "ScreenInteractive",
"kind": "INTERNAL",
"startTimeUnixNano": "1732737008000000000",
"endTimeUnixNano": "1732737008500000000",
"duration": "500ms",
"attributes": {
"pulse.type": "screen_interactive",
"screen.name": "Profile",
"routeKey": "Profile-Oht5epi34smXv9IlxNggQ",
"platform": "ios",
"session.id": "dce09977c69b0a5c15aa5fd01f817514"
}
}
Screen Session Tracking
Screen sessions track the time from when a screen comes into focus until the user navigates away or the app goes to background based on app state. This helps measure user engagement and identify screens where users spend the most time.
Generated Telemetry:
Type: Span
Span Name: ScreenSession
Span Kind: INTERNAL
Lifecycle
Span Start:
- Start Time (T0): When screen comes into focus (navigation ends)
- Triggered: Automatically when screen becomes active
- Condition:
screenSessionTracking: true(default) and app state isactive
Span End:
- End Time (T1): When user navigates away or app goes to background
- Triggered: Automatically on navigation dispatch or app state change
- Duration: T1 - T0 = Time user spent on screen
Note: If the app goes to background, the current session ends. When the app comes to foreground again, a new session automatically starts for the same screen.
Screen Session Attributes
| Attribute | Description | Example | Always Present |
|---|---|---|---|
pulse.type | Instrumentation type | "screen_session" | Yes |
screen.name | Current screen name | "Home", "Profile" | Yes |
routeKey | Unique route identifier | "Home-Oht5epi34smXv9IlxNggQ" | Yes |
Sample Screen Session Payload
{
"name": "ScreenSession",
"kind": "INTERNAL",
"startTimeUnixNano": "1732737006000000000",
"endTimeUnixNano": "1732737008000000000",
"duration": "2000ms",
"attributes": {
"pulse.type": "screen_session",
"screen.name": "Home",
"routeKey": "Home-Oht5epi34smXv9IlxNggQ",
"platform": "android",
"session.id": "dce09977c69b0a5c15aa5fd01f817514"
}
}
Note: All spans include global attributes (service, device, OS, session, network carrier, etc.). See Global Attributes for complete list.