In Expo Router, when do you use <Link href> instead of calling router.push, and what does Link's asChild prop change?
answer
- declarative tap versus code after logic
- href: string or { pathname, params }
- Link renders Text by default
- asChild forwards onPress to one child
- external URLs go to the OS
basics
~20 sLink navigates declaratively when a tap should reach a known href and renders a real anchor on web; the router object suits navigation after logic, such as a finished request. asChild hands Link's press to one child, like a Pressable.
solid answer
~40 s`<Link href>` from `expo-router` is the declarative way to navigate: the `href` is a path string (`'/medicine/42'`) or an object with `pathname` and `params`, and on web it renders a real anchor with that URL. By default Link renders its children inside a `Text`, which is fine for text links but lays out `View` children badly; `asChild` makes Link forward its props (`onPress`, `href`, `role`) to its **single child**, so a `Pressable` card becomes the link. Use `router` (the `router` export or `useRouter()`) when navigation must happen **after logic**: once a form submits, a request succeeds, or a condition is checked. Both take the same `Href`, both go through the same routing, and an absolute URL such as an `https:` or `tel:` link is handed to the operating system instead of the router.
code
tsx · 24 lines// src/components/medicine-card.tsx
import { Link, router } from 'expo-router';
import { Pressable, Text, View } from 'react-native';
export function MedicineCard({ id, name }: { id: string; name: string }) {
return (
<View>
<Link href={{ pathname: '/medicine/[id]', params: { id, from: 'search' } }} asChild>
<Pressable>
<Text>{name}</Text>
</Pressable>
</Link>
<Pressable
onPress={async () => {
await addToCart(id);
router.push('/cart'); // navigate only after the request succeeds
}}>
<Text>Add to cart</Text>
</Pressable>
</View>
);
}
declare function addToCart(id: string): Promise<void>;go deeper
Recall that Link navigates declaratively with an href string or { pathname, params } object, and that asChild hands the press to a single Pressable child.
Explain how href objects resolve to paths and query strings, why Link's default Text wrapper breaks View children, and when logic calls for the router object.
Keep navigation declarative where possible for web anchors and review clarity, and treat hrefs from data as untrusted because absolute URLs leave the app.
Set conventions for where navigation decisions live, balancing declarative links against imperative flows so navigation stays discoverable in a large codebase.
## Two ways to trigger the same navigation Expo Router gives you one routing engine and two ways to call it: - **`<Link href>`**: a component. The user taps it and navigation happens. It is declarative, visible in the component tree, and on web it renders an actual anchor element with a real URL, so middle-click, "open in new tab" and crawlers work. - **The `router` object**: functions such as `router.push`, `router.navigate` and `router.replace`, available as the `router` export from `expo-router` or through the `useRouter()` hook. It is imperative: you call it from code. Both accept the same **`Href`** type and end up in the same routing queue, so there is no difference in what screen appears; the choice is about **where the decision is made**. ## The href: string or object An `href` can be written two ways: 1. A **path string**: `'/medicine/42'`, `'/cart'`, `'/search?q=ibuprofen'`. 2. An **object** with `pathname` and optional `params`: `{ pathname: '/medicine/[id]', params: { id: 42, from: 'search' } }`. In the object form, a param whose name matches a bracket in the pathname fills that segment, and every other param becomes a **query parameter**, so the example resolves to `/medicine/42?from=search`. The object form avoids hand-building strings and escaping values, and typed routes can check it. ## Link's rendering and asChild By default Link renders its children **inside a `Text`**. That suits inline text links, but a `View`-based card inside a `Text` has surprising layout. The **`asChild`** prop changes this: - Link renders **no wrapper** of its own; - it forwards its props, including `onPress`, `href` and `role`, to its **single child**; - the child must accept `onPress` (or `onClick` on web), which `Pressable` does. Passing more than one child, or a child that cannot receive the press handler, is an error: Link reports that with `asChild` you must pass a single child element that emits `onPress`. | Need | Use | |---|---| | Inline text link | `<Link href="/cart">View cart</Link>` | | Tappable card or button | `<Link href="/medicine/42" asChild><Pressable>...</Pressable></Link>` | | Navigate after a request or check | `router.push(...)` / `router.replace(...)` in a handler | | Navigate on screen focus with no UI | `<Redirect href>` or `router.replace` in a focus effect | ## When the router object is the right tool - **After async work**: submit a prescription upload, await the response, then navigate to the confirmation. - **Conditional destinations**: route to `/cart` or `/sign-in` depending on state known only at press time. - **Non-UI triggers**: a notification handler or a timer, where there is nothing to render. - **Stack control**: `back`, `dismiss`, `dismissTo` and `dismissAll` have no Link equivalent except the `dismissTo` prop. Prefer Link for anything that is simply "tap goes to URL": it is easier to read, works as a real link on web, and supports extras such as `prefetch`, which renders the target screen off-screen ahead of time. ## External and special URLs If an `href` starts with a well-known scheme such as `https:`, `mailto:` or `tel:`, with any scheme followed by `//`, or with a bare `//`, Expo Router does **not** route it. It hands the URL to the operating system through React Native's `Linking`, so `<Link href="tel:+15550100">Call pharmacy</Link>` opens the dialler. This matters when an href comes from data: an unexpected absolute URL leaves the app instead of failing. ## Link on web versus native The same `<Link>` behaves appropriately per platform: - On **web** it renders an anchor whose `href` attribute is the resolved URL with route-group segments stripped, and it has the `link` role. A plain left click is routed in-app, while clicks with modifier keys, or with a `target` such as `_blank`, are left to the browser, so "open in new tab" works as users expect. - On **native** it renders a pressable `Text` (or your `asChild` child) with the `link` role, which screen readers announce as a link. - The **`prefetch`** prop asks Expo Router to render the target screen off-screen when the Link renders on a focused screen, so the first tap on a heavy screen, such as a medicine detail page with images, feels instant. A prefetched screen cannot use the imperative router or change its options until it is actually navigated to. These are reasons to keep ordinary navigation declarative: the router object gives none of them for free. ## Common mistakes - Wrapping a `Pressable` in Link **without** `asChild`, then fighting nested-press and layout issues. - Building `'/medicine/' + name` by hand, forgetting to encode spaces; the object form encodes values. - Calling `router.push` during render instead of in a handler or effect. - Using the router object for every tap, losing web anchors and making navigation harder to spot in review.
- What URL does { pathname: '/medicine/[id]', params: { id: '42', from: 'search' } } resolve to in Expo Router?`/medicine/42?from=search`. The `id` param fills the matching bracket segment and is URL-encoded; any param that does not match a bracket, here `from`, is appended as a query parameter.
- What happens if a Link's href is 'https://example.com/leaflet.pdf'?Expo Router treats an href with a scheme as external and hands it to the operating system through `Linking`, so the browser or another app opens it. It never becomes an in-app route, which is why hrefs built from untrusted data need checking.
Link is a signpost fixed to a door: you walk through when you choose. The router object is a receptionist who sends you on only after checking your paperwork.
saying these in an interview costs you the question
- Link and router.push lead to different routing engines.
- asChild lets Link wrap several sibling children at once.
- Link renders its children inside a View by default.
- Params in an href object must all match bracket segments.
- An https: href opens inside the app as a route.