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
fromof the next transition. A → Ais 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
| Symptom | Likely cause |
|---|---|
| No navigation crumbs at all | The integration is not in config.integrations, or it was imported from the package root instead of the subpath. |
| Trail starts at the second screen | onReady is not passed to NavigationContainer. |
| Every tab recorded once at startup, never again | Screens stay mounted — pass focused rather than relying on mount. |
| Crumbs stop when the reporter opens | Expected. The trail is frozen so the reporter’s own UI never pollutes the evidence. |