skip to content

Bracketed Path Segments

[id] and [...rest] files capture URL segments that screens read as string params. Interviewers ask how useLocalSearchParams and useGlobalSearchParams differ and what typed routes check.

part ofExpo (React Native)overview, primer and where to startread it →
on this pageshow

explore

questions

5

In Expo Router, how does a src/app/product/[id].tsx file capture a URL segment, and how does the screen read it?

level: juniorimportance: must knowfreq 60%

answer

  1. square brackets name a parameter
  2. one segment per [param]
  3. read with useLocalSearchParams
  4. the value arrives as a string
  5. static files win over [id]

basics

~10 s

Square brackets make a dynamic segment: src/app/product/[id].tsx matches /product/42 and any other single segment, and the screen reads it with useLocalSearchParams(), which returns id as the string '42'.

solid answer

~40 s

In Expo Router, a file or folder whose name is wrapped in square brackets is a **dynamic route**. `src/app/product/[id].tsx` matches `/product/42`, `/product/abc` and any other **single** segment after `/product/`, and the bracketed name becomes the parameter key. Inside the screen, `useLocalSearchParams()` from `expo-router` returns `{ id: '42' }`, always a string, together with any search parameters such as `?ref=home`; a generic like `useLocalSearchParams<{ id: string; ref?: string }>()` types it. When the route matches, `id` is never nullish. Static routes rank ahead of dynamic ones, so a `product/new.tsx` file wins for `/product/new`. When the route parameter changes, for example pushing `/product/43`, the screen instance is a new one, so code that keys data loading on `id` gets a fresh mount.

code

tsx · 15 lines
tsx
// src/app/product/[id].tsx
import { useLocalSearchParams } from 'expo-router';
import { Text, View } from 'react-native';

export default function ProductScreen() {
  const { id, ref } = useLocalSearchParams<{ id: string; ref?: string }>();
  const numericId = Number(id); // the param is always a string

  return (
    <View>
      <Text>Product {numericId}</Text>
      {ref ? <Text>Opened from {ref}</Text> : null}
    </View>
  );
}

go deeper

for a junior

Recall that [id].tsx captures one URL segment and that useLocalSearchParams returns it as a string alongside any query parameters.

for a middle

Explain matching order (static before dynamic), dynamic folders for nested paths, URI decoding, and that the generic is a type assertion only.

for a senior

Use the remount-per-param behaviour deliberately for data loading and caching, validate ids from untrusted links, and avoid reserved parameter names.

for a principal

Decide which identifiers belong in the path versus the query string, since path params become part of every shareable link the product must keep working.

## Dynamic segments in the file name Expo Router maps files under `src/app` to URLs. A name wrapped in **square brackets** turns that segment into a **route parameter** instead of a fixed word: - `src/app/product/[id].tsx` matches `/product/42`, `/product/sku-991` and `/product/abc`, but **not** `/product` or `/product/42/reviews`; a single bracket pair captures exactly **one** segment. - The text inside the brackets is the parameter **name**: `[id]` produces `id`, `[productId]` would produce `productId`. - A **folder** can be dynamic too: `src/app/product/[id]/reviews.tsx` matches `/product/42/reviews`, and every file inside that folder can read `id`. Route parameters are what make a route match. **Search parameters** (the query string, `?ref=home`) never affect matching; they ride along with any route. ## Reading the value in the screen `useLocalSearchParams()` from `expo-router` returns an object that merges **route and search parameters** for the screen it is called in: ```tsx // URL: /product/42?ref=home const { id, ref } = useLocalSearchParams<{ id: string; ref?: string }>(); // id === '42', ref === 'home' ``` Four details matter in practice: 1. **The value is a string.** URLs carry text, so `id` is `'42'`, not the number `42`; convert explicitly before arithmetic or strict comparisons. 2. **A matched route parameter is never nullish.** If the screen rendered, `id` exists. Search parameters are optional and may be `undefined`. 3. **Values are URI-decoded.** `/product/red%20mug` arrives as `'red mug'`. 4. **The generic is only a type assertion.** `useLocalSearchParams<{ id: string }>()` describes the shape to TypeScript; it does not validate or convert the value at runtime. ## Which route wins When several files could match a URL, Expo Router ranks them: | URL | Files present | Match | |---|---|---| | `/product/new` | `product/new.tsx`, `product/[id].tsx` | `new.tsx` (static beats dynamic) | | `/product/42` | `product/new.tsx`, `product/[id].tsx` | `[id].tsx` with `id: '42'` | | `/product/42/reviews` | `product/[id].tsx` only | not matched by `[id].tsx`; needs `[id]/reviews.tsx` or a catch-all | Because static routes rank first, reserving words such as `new` or `edit` as sibling files is safe; they can never be read as an id. ## Remounting and data loading When a **route parameter** changes, the screen is a different route instance. Pushing `/product/43` from `/product/42` in a stack mounts a **new** screen on top, and the first one keeps its own `id: '42'` underneath. Expo's guide states it plainly: whenever a route parameter changes, the component remounts. That is why keying data fetching on `id` is safe: each product screen owns its own data, and going back shows the previous product without refetching. ## A worked example: the shop's product routes A small shop app might lay out its product routes like this: ```text src/app/product/new.tsx -> /product/new src/app/product/[id]/index.tsx -> /product/42 src/app/product/[id]/reviews.tsx -> /product/42/reviews ``` Walking through it: - `/product/new` hits the static file, so the "create product" form can never be mistaken for a product with the id `new`. - `/product/42` hits the dynamic folder's `index.tsx`, which reads `id: '42'`. - `/product/42/reviews` hits `reviews.tsx` inside the same dynamic folder, which also reads `id: '42'` because the parameter belongs to the folder name. - `/product/42/photos` matches nothing and falls through to the not-found screen until a `photos.tsx` is added. The layout makes the URL design explicit: each resource has one canonical path, sub-pages hang off the same parameter, and reserved words live beside the parameter as static files. Interviewers often ask candidates to sketch exactly this kind of tree from a list of URLs. ## Names to avoid A few parameter names are reserved for internal use by Expo Router and React Navigation: **`screen`**, **`params`**, **`initial`** and **`state`**. A file named `[screen].tsx` collides with them, so pick a descriptive name such as `[screenId]`. The URL hash is also exposed as a special parameter named `#`. ## What interviewers probe - Can the candidate predict which file answers a URL, including the static-beats-dynamic rule? - Do they know the value is a string and that the TypeScript generic does not convert it? - Do they know `[id]` captures one segment, and that deeper paths need a nested file or a catch-all? - Do they see that each change of `id` produces a new screen instance, which shapes caching and back navigation?

  • How would /product/42/reviews be served next to src/app/product/[id].tsx?
    Move the product screen into a dynamic folder: `src/app/product/[id]/index.tsx` for `/product/42` and `src/app/product/[id]/reviews.tsx` for `/product/42/reviews`. Both files read `id` with `useLocalSearchParams()`.
  • Why might a screen named src/app/[screen].tsx behave strangely?
    `screen`, `params`, `initial` and `state` are reserved parameter names used internally by Expo Router and React Navigation. A route parameter with one of those names can collide with navigation internals, so choose a distinct name such as `[screenId]`.

A form with one blank to fill in: /product/___ accepts any single word written in the blank, but a pre-printed form for /product/new is always picked first when the word matches it exactly.

saying these in an interview costs you the question

  • useLocalSearchParams returns the number 42 for /product/42.
  • [id].tsx also matches /product/42/reviews.
  • A dynamic [id].tsx overrides a static new.tsx in the same folder.
  • The TypeScript generic on useLocalSearchParams converts values at runtime.
  • Search parameters decide which route file matches the URL.
open as a page

In Expo Router, how do useLocalSearchParams and useGlobalSearchParams differ when several /product/[id] screens are stacked, and which should a screen use?

level: middleimportance: must knowfreq 50%

basics

~20 s

useLocalSearchParams returns the params of the route the component belongs to and ignores other screens' URLs; useGlobalSearchParams follows the currently focused URL and re-renders background screens on every change, so screens should use the local hook.

open as a page

In Expo Router, what do usePathname and useSegments return on /product/42, and when would you reach for each?

level: middleimportance: should knowfreq 32%

basics

~20 s

usePathname returns the normalized URL path without the query, '/product/42', while useSegments returns the matched file segments as written, ['product', '[id]'], including any (group) names, so pathname suits display and analytics and segments suit structural checks.

open as a page

In Expo Router, why are params always strings or string arrays, and what does a src/app/docs/[...slug].tsx route receive for /docs/guides/install?

level: middleimportance: should knowfreq 38%

basics

~10 s

Expo Router resolves every navigation to a URL and parses params back out of it, so values are strings; a [...slug] catch-all collects all remaining segments into a string array, giving slug ['guides', 'install'].

open as a page

In an Expo Router project, what does enabling experiments.typedRoutes check at compile time, and how do you keep it working in CI?

level: seniorimportance: nice to knowfreq 28%

basics

~10 s

With experiments.typedRoutes, Expo CLI generates route types from src/app, so TypeScript rejects hrefs to missing routes, dynamic hrefs with wrong params and relative paths; CI must run npx expo customize tsconfig.json before type checking.

open as a page