skip to content

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%

answer

  1. several screens stay mounted in a stack
  2. local: this screen's own URL
  3. global: whatever URL is focused
  4. global re-renders background screens
  5. global suits analytics

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.

solid answer

~40 s

A stack keeps earlier screens mounted, so after pushing `/product/2` on top of `/product/1` both product screens are alive. `useLocalSearchParams()` returns the parameters **of the route that component belongs to**: the screen underneath keeps `id: '1'` and does not update while another URL is focused. `useGlobalSearchParams()` returns the parameters of the **currently focused URL** wherever it is called, so the background screen suddenly reads `id: '2'` and re-renders on every URL change. That causes wrong data (the old screen fetches product 2) and wasted renders. Screens should use the local hook; the global one is for components that genuinely track the whole app's URL, such as analytics or a layout-level indicator. Both return the same shape and accept the same generic; only what they follow and how often they update differ.

code

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

export default function ProductScreen() {
  const local = useLocalSearchParams<{ id: string }>();
  const global = useGlobalSearchParams<{ id: string }>();

  // After pushing /product/2 over /product/1, the screen underneath logs
  // local '1' and global '2', and re-renders because of the global hook.
  console.log('local', local.id, 'global', global.id);

  return (
    <View>
      <Text>Product {local.id}</Text>
    </View>
  );
}

go deeper

for a junior

Recall that useLocalSearchParams gives a screen its own parameters and is the default for screens, while useGlobalSearchParams follows whatever URL is focused.

for a middle

Explain why stacks and tabs keep several screens mounted, and walk through what each hook returns in the background screen after a push.

for a senior

Diagnose wrong-product-on-back and render storms caused by the global hook in screens, and place global reads only in analytics or layout code.

for a principal

Set a codebase rule for URL-derived state so screens own their parameters and app-wide observers are few, cheap and centralised.

## Why two hooks exist In a native app several screens are **mounted at the same time**. A stack keeps every pushed screen alive underneath the top one, and a tab navigator normally keeps visited tabs mounted while you switch. The URL, however, describes only the **focused** screen. Expo Router therefore offers two views of URL parameters: - **`useLocalSearchParams()`**: the parameters of the route the calling component is rendered in. It only updates when the global URL matches that route. - **`useGlobalSearchParams()`**: the parameters of the globally selected route, wherever the hook is called. It updates on every URL parameter change, even when the calling screen is in the background. Both are imported from `expo-router`, return the same shape (route and search parameters together), and accept the same generic for typing. The difference is **which URL they follow** and **how often they re-render**. ## Walking through a stack The shop has `src/app/product/[id].tsx` inside a root `Stack`. The user opens product 1, then taps a "related product" link to product 2: 1. `/product/1` is focused. Local and global both read `id: '1'`. 2. `/product/2` is pushed. The **new** screen reads `id: '2'` from both hooks. 3. The **old** screen is still mounted. Its local hook still reads `'1'`; its global hook now reads `'2'` and the component re-renders. 4. Pushing `/product/3` repeats the pattern: every background screen calling the global hook re-renders again, in stack order. Expo's URL-parameters guide shows exactly this: after a push, the first screen logs its own local value next to the new screen's global value. ## What goes wrong with the global hook in a screen | Symptom | Cause | |---|---| | Going back to product 1 shows product 2's details | The background screen refetched using the global `id` | | Scrolling stutters after each navigation | Every background screen re-renders on every URL change | | An effect keyed on `id` fires on screens nobody sees | The global value changed under them | The local hook avoids all three. Because it keeps the screen's own parameters while other screens are focused, the previous product's data is still there when the user navigates back, with no refetch. ## When the global hook is right The global hook exists for code that is about **the app's current location**, not about one screen: - analytics or logging that records every URL the user visits; - a component in a layout, such as a header badge, that reflects the focused route's parameters; - background work that must react to the URL whatever screen is on top. Expo's API docs describe it as useful for analytics and other background operations that do not draw to the screen, and advise opting towards the local hook when reading parameters in a stack. ## A layout-level example Suppose the shop's root layout shows a small "viewing product N" banner above the stack, or sends a screen-view event on every navigation. That code lives **outside** any single product screen and genuinely cares about whichever product is focused, so the global hook is correct there: - the layout calls `useGlobalSearchParams()` once; - it re-renders on every URL change, which is exactly what the banner or event needs; - the product screens underneath keep calling `useLocalSearchParams()` and stay stable. The rule of thumb that falls out of this: **one global observer high in the tree, local reads everywhere else**. If a performance profile shows many screens re-rendering on every navigation, a stray `useGlobalSearchParams()` inside a screen or a list item is one of the first things to search for. Replacing it with the local hook usually removes both the renders and the wrong-data bug in one change, because the two symptoms have the same cause. ## Typing both Both hooks take the same generics. A manual shape such as `useLocalSearchParams<{ id: string; ref?: string }>()` works everywhere; with typed routes enabled, passing a route such as `'/product/[id]'` derives the route parameter types from the file tree. The generic is a type assertion only; neither hook validates values at runtime. ## Interview checklist - State that stacks and tabs keep several screens mounted, which is the reason two hooks exist. - Say which one a screen uses (local) and why (stable params, fewer renders, correct back navigation). - Name a legitimate use of the global hook. - Note that both return strings or string arrays, never numbers.

  • Why does useLocalSearchParams help when the user navigates back to an earlier product?
    The earlier screen kept its own `id` while other products were focused, so any data it loaded for that id is still valid and on screen. Nothing refetches or flickers on back navigation, which would happen if the screen had followed the global URL.
  • Where is useGlobalSearchParams a sound choice?
    In code that tracks the whole app's location rather than one screen: a screen-view analytics hook in the root layout, a layout-level header that reflects the focused route, or background logic reacting to URL changes. It re-renders on every URL change, so keep such components light.

saying these in an interview costs you the question

  • useLocalSearchParams and useGlobalSearchParams always return the same values.
  • useGlobalSearchParams is the faster hook because it skips the route context.
  • Background stack screens unmount, so the choice of hook does not matter.
  • useLocalSearchParams returns only route params and never query params.
  • The global hook is the right default for screen data fetching.