In React Navigation 7, why should route params hold an order id rather than the order object or an onDelivered callback?
answer
- params are navigation state
- development-only warning
- restoration and links need plain data
- a copy frozen at navigate time
- setOptions for header callbacks
basics
~20 sRoute params live in React Navigation's navigation state, which must stay serializable for persistence, deep links and tooling. An id is small and lets the screen read current data; an object copy goes stale and a function triggers a development warning.
solid answer
~50 sRoute params are not props; they are stored on the route in React Navigation's **navigation state**. That state is designed to be plain data so it can be persisted and restored, built from a deep link and inspected. In development React Navigation checks it and warns with *Non-serializable values were found in the navigation state* when it meets a function, a `Date`, a `Map` or a circular reference. An `onDelivered` callback fails that check and cannot be restored or come from a URL. The whole order object passes the check but is a copy frozen at navigate time, so the tracking screen shows a stale status while the real order moves on. Passing `orderId` keeps the state small and lets the screen read live data; results go back as params and header actions use `navigation.setOptions`.
go deeper
Recall the rule: pass ids and other plain values in params, not whole objects or functions, and read the data inside the screen.
Explain that params are stored in navigation state, what the development check flags and which features break when state is not plain data.
Show the replacements you use for each callback use case, results via params, header actions via setOptions, shared changes via the data layer, and why entity copies go stale.
Argue for serializable navigation as an architectural constraint, because it keeps restoration, deep links and later platform targets possible without rewriting screens.
## Params live in the navigation state In **React Navigation 7** the whole navigation tree is described by a **navigation state** object: navigators, their routes, and each route's `params`. When you call `navigation.navigate('Tracking', { orderId: 'A123' })`, the params object is stored on the new route in that state; the screen component receives it through its `route` prop. That makes params part of a data structure the library expects to be able to copy, store and rebuild, not an argument handed to a function. Three features depend on the state being **serializable**, meaning expressible as plain JSON-like data: 1. **State persistence and restoration**: an app can save the state and pass it back as the container's initial state on the next launch, so a user returns to the tracking screen they left. 2. **Deep links**: a URL is turned into a navigation state, and a URL can only carry strings, so any screen reachable from a link must be able to rebuild itself from plain values. 3. **Tooling**: developer tools that display or replay navigation state need data they can print. ## What the development warning checks In development builds React Navigation walks the state after each change and logs a warning, *Non-serializable values were found in the navigation state*, with the path of the offending value. The check accepts: - strings, numbers, booleans, `null` and `undefined`; - arrays and plain objects built from those. It flags: - **functions**, reported as `Function`; - objects that are not plain, such as a **`Date`**, a `Map` or a `Set`; - **circular references**. The check and the warning are skipped in production builds, so nothing crashes there; the cost shows up later as a restored or linked screen that cannot rebuild its callback, or a feature built on persistence that silently breaks. ## Why an id beats an entity A plain order object passes the check, but it is still the wrong thing to pass: | Param | Serializable | Stays current | Works from a deep link | |---|---|---|---| | `orderId: 'A123'` | yes | yes, the screen reads live data | yes, the URL can carry it | | the whole `order` object | yes, if plain | no, a copy taken at navigate time | no, a URL cannot carry it | | `onDelivered: () => refresh()` | no | not applicable | no | | `promisedAt: new Date()` | no | yes | only as a string | The entity problem is **staleness**: the params hold a snapshot, so when the courier marks the order as picked up, the tracking screen still shows the old status unless it re-reads the order anyway, at which point the copy was pointless. It also duplicates data that the app's data layer already owns. ## Size matters too Serializable is not the same as small. Every param is copied into the state that the development check walks after each change, that a persisting app writes out on every state change, and that a restore has to read back before the first screen renders. A tracking screen handed a list of two hundred route points in its params makes each of those steps heavier for no benefit, because the screen could load the same points by order id. Keep params to identifiers and a few flags: - ids of the entities the screen shows; - small view settings such as a selected tab; - short strings or numbers that a URL could also carry. ## What to use instead of a callback Callbacks in params usually try to do one of three jobs, and each has a serializable replacement: - **Returning a result** to the opener: navigate back with params, for example `popTo('Tracking', { orderId, note }, { merge: true })`, and let the opener react to the new param. - **Wiring a header button** to screen logic: set it from inside the screen with `navigation.setOptions({ headerRight: … })`, where functions are allowed because options are not part of the navigation state. - **Notifying other screens** that something changed: update the shared data layer, which every interested screen already reads. A `Date` becomes an ISO 8601 string or an epoch-milliseconds number, parsed back in the screen. ## When the warning seems harmless An app that never persists state and has no deep link into a screen may run fine with a callback param for a long time. The warning is still worth fixing, because the design choice is hard to undo once many screens depend on it, and adding deep links or state restoration later then means reworking every screen that relied on a function in its params.
- How do you pass the promised delivery time to the tracking screen?Pass it as a string in ISO 8601 form or as an epoch-milliseconds number, and turn it back into a `Date` inside the screen. A `Date` instance is not a plain object, so React Navigation's development check flags it, and a URL could never carry one.
- The tracking screen needs a header button that calls its own refresh logic; how do you wire it without a callback param?Set the header from inside the screen with `navigation.setOptions({ headerRight: () => <Button title="Refresh" onPress={refresh} /> })` in an effect. Options are not part of the navigation state, so functions are allowed there, and the button always calls the screen's current logic.
Params are like the label on a parcel: an order number on the label lets anyone look up the latest status, while taping a printout of the order to the box only shows how things looked when it was packed.
saying these in an interview costs you the question
- Passing the whole order object avoids a refetch and has no downside.
- The non-serializable warning means the release build will crash.
- Functions in params are fine because navigation state only lives in memory.
- A Date is a fine param because JSON.stringify can encode it.