React Native Flow

← Retour au blog

March 11, 2026·10 min read

Typing React Navigation params without losing your mind

Untyped navigation is stringly-typed bugs waiting for demo day. You can get surprisingly far with a single root param list and nested partial types—here’s the shape that’s worked for me on mid-size apps. The goal is one place to update when a route changes, and compile errors instead of silent wrong screens.

One source of truth

Define param lists next to the navigator that owns those routes. Export the type, import it in screens. When a route gains a param, you fix the type once and the compiler yells everywhere that needs updating.

Avoid `any` on `route.params`; use `undefined` unions for optional params instead.

Nested navigators

The parent stack usually only knows the child navigator’s entry screen. Model that explicitly instead of pretending every leaf route lives at the root.

Deep linking often maps to nested paths—your types and your URL structure should tell the same story.

Expo Router angle

File-based routes give you different ergonomics but the same idea: know your dynamic segments, validate search params, and don’t trust raw strings from the URL.

Generate types from a schema if the route table gets huge; hand-maintained lists stop scaling around twenty named routes.

Mistakes that still slip through types

Optional params modeled as empty object instead of `undefined` when absent—leads to “defined but wrong” bugs. Serializing non-JSON-safe values through params (functions, class instances) breaks at the boundary. Assuming web and native deep links parse query strings identically—test cold start on both.

Add a tiny runtime guard for external URLs (marketing UTM links) before you trust `route.params` as gospel.

Param lists as living API contracts

Treat your navigation param types like public API definitions. When a screen’s contract changes—new optional flag, renamed id—update the type once and let TypeScript surface every callsite that must adapt. This is most valuable when multiple teams touch the same navigator: the compiler becomes the coordination mechanism that standups cannot always provide. Avoid `any` on params; prefer explicit `undefined` for absent values rather than empty objects that pretend to be defined. Document param semantics in short comments where names are ambiguous: does `fromDeepLink` imply partial data? Should `resetStack` default false? Those questions belong next to the type, not in a forgotten wiki page. When you add validation—for example ensuring `userId` matches UUID format—do it at the navigation boundary or in the screen’s loader hook, not scattered across components. Consistency there prevents security issues when deep links supply untrusted input.

Nested navigators and partial typing strategies

Parent stacks often embed tab or drawer navigators. Type the parent with the child’s entry route, not every leaf route, unless the parent truly navigates to leaves directly. For deeply nested flows, consider path-based helpers that compose child navigators’ types instead of one gigantic union that obscures ownership. Expo Router users can lean on file-system routes but still need runtime validation for search params and dynamic segments—types alone cannot save you from malformed URLs. When sharing param types between web and native, watch for differences in serialization: dates, arrays, and nested objects may stringify differently. Provide mappers between URL query objects and strongly typed params. If you generate types from a schema, regenerate when routes change in CI to avoid drift. Hand-maintained lists work until about twenty routes; beyond that, invest in codegen or a single registry object that feeds both navigation and analytics screen names.

Deep links, cold start, and optional data

Cold-start deep links often deliver incomplete data: marketing adds query params, users bookmark truncated URLs, or legacy links omit new required fields. Model optional params honestly and gate rendering until you resolve async prerequisites—auth hydration, feature flags, remote config. Show skeleton UI rather than flashing the wrong stack. Log structured parse failures with the raw URL (scrub tokens) so support can diagnose without reproducing on device. For authenticated routes, decide what happens when the session expires between link tap and navigation handling: bounce to login with return path, or show an error with retry. Testing matrices should include: signed-in, signed-out, expired token, airplane mode. Navigation types should reflect these states where possible—e.g., union of `Ready` versus `PendingAuth` props—so screens do not assume happy paths only.

Refactors that touch route names

Renaming routes is painful but sometimes necessary for clarity. Do it with codemods or structured find-replace, and update analytics event names in the same change. Search for string literals beyond TypeScript—E2E tests, notification payloads, backend-driven route enums. Communicate renames to backend teams if they embed route names in push payloads. Provide a temporary alias layer only if you must support old binaries in the field; remove aliases after telemetry shows usage at zero. Document migration timelines so support knows how to interpret older app versions’ crashes. The goal is zero silent behavior change: users should see the same screens, engineers should see clearer names.

Interop with global state and context

Avoid stuffing non-serializable objects into route params—functions, class instances, big class caches. Pass ids and hydrate inside screens from query caches or global stores. If you must pass callbacks for ephemeral flows, document that they cannot traverse persistence boundaries. Context providers should align with navigator boundaries: a provider at the tab level versus root changes which screens re-render on updates. Type those contexts narrowly so navigation params and global state do not duplicate the same data with divergent sources of truth. When they must overlap, pick one owner—usually the store—and let params be a one-time bootstrap key.

Team workflows and code review tips

In PR review, verify new routes include types, tests for param validation, and updates to deep link documentation. Check that back navigation behaves on Android hardware back and iOS interactive pop. Confirm screen readers announce titles consistent with route names for accessibility. Junior-friendly feedback includes pointing to example PRs that did navigation well—patterns beat lectures. Establish a ‘navigation champion’ on rotation who answers questions quickly; blocked navigation work blocks releases. Finally, measure incident rates tied to navigation mistakes; if they spike after a big change, schedule a postmortem focused on process, not blame.

Long-term maintenance and tooling upgrades

React Navigation and Expo Router evolve; upgrade notes often include type changes. Budget time each major bump to fix compile errors deliberately rather than suppressing them. Keep an eye on community typings for third-party navigators; contribute back when you patch edge cases. If you maintain multiple apps, share a typed navigation package internally to avoid duplicating effort. Periodically delete dead routes and params—legacy cruft confuses new hires and bloats bundles. Automated route usage reports from analytics can highlight screens nobody visits, candidates for removal. Navigation typing is never ‘done’; it is an ongoing hygiene task that pays dividends in fewer production surprises.

Shipping and reliability habits (1)

Performance work should start with measurement, not instinct. Watch JS thread versus UI thread separately; they bottleneck differently. Lists, images, and animations dominate most regressions—optimize those before micro-optimizing pure functions. Hermes, JSC, and bridge internals evolve; re-profile after every major upgrade instead of trusting last year’s numbers. Battery and thermal throttling on mid devices reveal issues flagship phones hide.

Security and privacy expectations move faster than roadmaps. Treat analytics, crash, and attribution SDKs as part of your threat model: initialize them deliberately, document data flows, and verify ‘off’ truly stops network calls. Client-side secrets are public secrets—anything shipped in an APK or IPA should be assumed extractable. Pair mobile changes with backend policies so authorization remains consistent across platforms.

Platform differences worth rehearsing (2)

Images and maps look simple in mocks and expensive in production. Decode sizes should match display sizes; static map thumbnails are not interchangeable with interactive MapView gestures. Quotas, API keys, and offline behavior need explicit fallbacks—addresses as text, retry buttons, and calm error copy. Monitor vendor dashboards for spikes that indicate bugs or abuse.

Error boundaries catch render failures, not native crashes or async mistakes. Pair them with platform crash reporting and structured client logs. Fallback UI should include build identifiers and humane copy—never raw stack traces for end users. Test fallbacks with screen readers; a broken error screen is still broken UX.

Security, privacy, and data handling (3)

FlatList performance is configuration as much as code. Stable keys, reasonable `windowSize`, and memoized rows beat switching to a different list primitive blindly. Nested virtualized lists are a last resort—redesign first. Profile with production-like data volumes; dev placeholders lie.

Splash screens and launch gates should reflect honest readiness: fonts, theme, session, critical remote config—not every SDK under the sun. Infinite splash is worse than a slightly longer branded hold. Match native and JS background colors to avoid flashes; respect reduced motion for animations.

Performance and measurement discipline (4)

Analytics schema governance prevents warehouse disasters: version events, avoid high-cardinality strings, and align names across iOS, Android, and web. Consent gating must stop network calls, not just UI. Separate dev and prod projects to avoid polluting dashboards.

Testing onboarding changes with funnel metrics beats debating opinions. Segment by acquisition channel and platform; back behavior differs. Skip paths must be genuine—dark patterns may win short metrics and destroy brand trust. Localization length tests prevent clipped CTAs in verbose languages.

Team process and long-term maintenance (5)

Helper modules concentrate glue code—storage, navigation, permissions—so screens stay readable. Split helpers by topic before files become merge-conflict magnets, and document each module’s contract. Good helpers answer ‘where do we save tokens?’ in one glance—not ‘ask Sarah.’

Shipping React Native features is less about any single API and more about the system around it: typed boundaries, predictable navigation, and telemetry that tells you what broke in production. Prefer boring, explicit modules over clever metaprogramming that the next hire cannot grep. When platform vendors change behavior in point releases, your defense is automated smoke tests on real devices and a short internal changelog of native assumptions you rely on.

Shipping and reliability habits (6)

Deep links are a cross-team system: marketing URLs, hosted association files, entitlements, router params, and analytics query preservation. Debug with structured logging of raw URLs (scrub secrets) and reproduce cold-start races with auth hydration. Staging and production should be obviously separated—accidentally opening prod from a QA link erodes trust and pollutes data.

Push notifications walk a line between helpful and intrusive. Prime users with context, respect notification channels on Android, and measure opt-outs after campaigns—spikes mean copy or frequency problems. Payload design affects background behavior; test killed and locked-device states. Tokens belong server-side with rotation strategies; never treat the client as authoritative for subscription state.

Platform differences worth rehearsing (7)

Keyboard and form UX separate polished apps from ‘works on desktop simulators.’ Platform differences in soft input modes matter; test smallest phones and Android gesture navigation. Primary actions must remain reachable when the keyboard is visible—scroll containers and keyboard controllers exist because this problem is universal.

Type-safe navigation pays off when routes multiply. Keep param lists near navigators, validate external URLs, and avoid serializing non-JSON-safe values through params. Renaming routes is a cross-cutting change—update analytics, push payloads, and E2E selectors in the same release train.

Security, privacy, and data handling (8)

Hermes versus JSC is not a lifestyle choice—profile your app. Hermes usually wins on startup; some libraries still assume JSC quirks. Engine toggles are not substitutes for fixing quadratic renders in your own code. Upgrade notes matter: Intl support and debugging tooling evolve.

Reanimated and gesture libraries earn their place when profiling proves UI-thread work and your team can maintain native upgrades. Worklets have constraints—read errors carefully. Respect reduced motion and test Android timing differences—identical JS does not guarantee identical feel.

Performance and measurement discipline (9)

Environment variables should be classified: public-by-design, sensitive-with-mitigations, or never-on-device. `EXPO_PUBLIC_` values are extractable—treat them that way. Align env handling across EAS profiles and local dev; fail fast when keys are missing instead of shipping undefined behavior.

Project structure should make ownership obvious: routes as backbone, feature folders for product areas, thin screens, and shared infrastructure that is deliberately named. Refactor in vertical slices with device-tested releases—big-bang rewrites without tests are how teams lose weeks.

Sponsorisé

Promo rapide