Skip to content
TraceItX Docs
Documentation

Screen tracking

The one thing that is not automatic. An RN app is a single native screen for its whole lifetime.

Updated

There is nothing for the native side to observe: one UIViewController, one Activity, for the life of the process. Navigator state lives only in JavaScript, and TraceItX installs nothing into your JS runtime unless you ask.

So this is explicit — and it is the step worth not skipping, because without it every report arrives with no trail of where the user had been.

react-navigation and expo-router

expo-router is react-navigation underneath, so one wiring covers both:

import { TraceItXProvider } from '@traceitx/react-native';
import { reactNavigationIntegration } from '@traceitx/react-native/integrations/react-navigation';
import { NavigationContainer, createNavigationContainerRef } from '@react-navigation/native';

const navigationRef = createNavigationContainerRef();
const txNav = reactNavigationIntegration({ navigationRef });

export default function App() {
  return (
    <TraceItXProvider config={{ apiKey, integrations: [txNav] }}>
      <NavigationContainer ref={navigationRef} onReady={txNav.onReady}>
        {/* … your stack … */}
      </NavigationContainer>
    </TraceItXProvider>
  );
}

Two details decide whether this works:

The integration is a subpath export. It is not exported from the package root, so importing it from there is a module resolution error rather than a silent undefined. It lives at @traceitx/react-native/integrations/react-navigation so it never enters your bundle unused.

onReady is what records the initial route. The provider mounts before the navigation container is ready, so without it the trail starts at the user’s second screen.

Add onStateChange={txNav.onStateChange} as well if your react-navigation version’s ref listener proves unreliable — duplicate markers for the same screen are suppressed, so double-wiring is harmless.

Any other navigator

Wix react-native-navigation, react-router-native, hand-rolled tab state — all one line per screen:

import { useTXScreen, recordScreen } from '@traceitx/react-native';

// Navigators that unmount hidden screens — mount is the appearance.
function CheckoutScreen() {
  useTXScreen('Checkout');

}

// Stacks and tabs that keep screens mounted — pass focus instead.
useTXScreen(route.name, { focused: useIsFocused() });

// Wix react-native-navigation.
componentDidAppear() { recordScreen(this.props.screenName); }

<TXScreen name="Checkout" /> is the declarative form for class components. Both feed the same chain as the integration.

The focused distinction matters more than it looks: in a tab navigator every screen stays mounted, so mount-time marking would record all of them once at startup and nothing thereafter.

How the chain behaves

  • One global chain, chronological. Not per-stack — a tab switch correctly reads TabA → TabB, which per-stack scoping would hide.
  • The first screen emits nothing. There is no screen to come from yet. It still becomes the from of the next transition.
  • A → A is suppressed. Backgrounding, refocus, recomposition or a double-wired integration never produce a crumb.
  • Blank names are dropped, and markers are no-ops before the SDK starts.
  • Failures stay silent. Marker calls run inside your navigator’s own dispatch, so they never throw into host navigation. A misconfigured integration logs a warning and captures nothing.

Naming screens

Route identifiers, never user content. OrderDetail, not Order #4471 — jane@example.com. Screen names do pass through the breadcrumb buffer’s redaction, so an obvious email or card number is caught — but redaction recognises shapes, not meaning, and an order id or an internal username sails through. Do not rely on it. Ids belong in the optional data map.

What lands in the envelope

{
  "t": 1755500000000,
  "seq": 42,
  "kind": "navigation",
  "message": "Cart → Checkout",
  "data": {
    "from": "Cart",
    "to": "Checkout"
  }
}

t is the shared epoch-millisecond clock that also stamps the session replay.

Troubleshooting

SymptomLikely cause
No navigation crumbs at allThe integration is not in config.integrations, or it was imported from the package root instead of the subpath.
Trail starts at the second screenonReady is not passed to NavigationContainer.
Every tab recorded once at startup, never againScreens stay mounted — pass focused rather than relying on mount.
Crumbs stop when the reporter opensExpected. The trail is frozen so the reporter’s own UI never pollutes the evidence.