Lesson 15 of 26
Passing Params and Conditional Navigation
Passing params to a screen instead of prop-drilling through the navigator, and the root-level auth-stack-vs-app-stack pattern for conditional navigation.
Two screens connected by a navigator usually need to share more than a simple "go here" — the destination screen almost always needs which thing to show, and the app as a whole often needs to show an entirely different set of screens depending on who's using it. Both of those are handled by patterns that are durable across navigation libraries and versions, even though this lesson deliberately won't pin down one exact current API signature.
Passing params
When you navigate to a screen, you can hand it data specific to that navigation — conceptually, something like:
// From a list screen, navigating to a detail screen for one specific item
navigation.navigate("Details", { id: 42 });The destination screen reads that data back through a mechanism the navigator provides for exactly this — conceptually, a route object or hook tied to that screen, rather than the screen receiving it as an ordinary prop handed down by a parent component:
function DetailsScreen({ route }: { route: { params: { id: number } } }) {
const { id } = route.params;
const { data: product } = useProduct(id);
return <ProductView product={product} />;
}Why this beats prop-drilling
Without params, getting id to DetailsScreen would mean threading it as
a prop through every component between wherever it originated and
DetailsScreen itself — components that don't use id for anything, just
forwarding it downward because something further down needs it. That's
prop-drilling, and it's exactly the kind of coupling params sidestep: the
navigator carries the value directly from the call site (navigate("Details", { id: 42 })) to the destination screen, with nothing in between required
to know or care that id even exists.
This should feel familiar from plain React: it's the same problem
useContext solves for state that many distant components need, applied
here specifically to "the data one particular navigation needs to carry
along with it."
Conditional navigation: auth flows
The other durable pattern is switching what the user can navigate to at all, based on some piece of app state — most commonly, whether they're logged in.
The tempting-but-wrong instinct is to guard screens one at a time: check
isLoggedIn inside SettingsScreen, inside CheckoutScreen, inside every
other screen that shouldn't be reachable while logged out, redirecting to
login if the check fails. This works, technically, but it's a rule
duplicated across every protected screen — and a rule duplicated
everywhere is a rule that's eventually missed somewhere, the first time a
new protected screen is added without remembering to add the same check.
The standard pattern instead switches the navigator itself, at the root, based on auth state:
function RootNavigator() {
const { isLoggedIn } = useAuth();
return isLoggedIn ? <AppNavigator /> : <AuthNavigator />;
}AuthNavigator is its own stack — login, signup, forgot-password — and
AppNavigator is the real app's navigator, tabs, stacks, and all. An
unauthenticated user never has AppNavigator mounted in the first place,
which means there's no protected screen anywhere inside it that could be
reached by accident: there's structurally nothing to guard against,
because none of it exists in the tree until isLoggedIn flips.
function useAuth() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
// ...sign-in / sign-out logic that flips isLoggedIn...
return { isLoggedIn };
}The moment isLoggedIn becomes true (say, right after a successful
login call inside AuthNavigator), RootNavigator re-renders, swaps in
AppNavigator, and the user lands in the real app — with no individual
screen ever having had to ask "am I allowed to be here?"
What to remember
- Pass data a destination screen needs as navigation params, rather than prop-drilling it through every screen in between.
- The destination screen reads params back through the navigator's own mechanism (a route object/hook), not as an ordinary prop from its parent.
- Auth flows are handled by rendering an entirely different navigator (an auth stack vs. the main app navigator), switched once at the root based on auth state.
- Switching at the root is structurally safer than guarding individual screens, because there's nothing to forget to guard — the protected navigator simply isn't mounted for a logged-out user.
- These are durable concepts across navigation library versions; the exact function names and signatures are the part that changes.
Check yourself
4 questions · pass 3/4 to unlock Deep Linking, Conceptually
1.Why pass params when navigating to a screen, instead of prop-drilling the data through the whole navigator tree?
2.Conceptually, how does the destination screen read params back?
3.What's the standard real-world pattern for switching between authenticated and unauthenticated navigation?
4.What's a downside of guarding individual screens one-by-one with their own auth checks, compared to switching navigators at the root?
4 left to answer