Singular stack routes

Edit page

Learn how to enforce singular routes.


Singular routes are currently flagged as dangerouslySingular due to an upstream issue with react-native-screens. Expo is working with Software Mansion to resolve the issue.

While a Stack normally operates with push/pop actions, in advanced scenarios you may wish to enforce a singular version of a screen in the stack. When enabled, any time a new screen is pushed the history will be scanned and any matching screens will be removed to ensure only a singular version exists.

Options

There are two options for conditions:

  • true: Use the default singular function
  • Function: Specify your own singular function

The default singular function returns the screen's pathname without search params or the hash.

The singular identifier function has the signature of (routeName: string, params: Record<string, string | string[]>) => string | undefined.

Assigning singular on the navigator

app
_layout.tsx
index.tsx
[profile].tsx

Using the above example app, you can create a singular constraint to ensure that each [profile] page is unique.

app/_layout.tsx
export default function () { return ( <Stack> <Stack.Screen name="[profile]" dangerouslySingular={({ profile }) => profile} /> </Stack> ); }

If you now navigate to /profile-2 and your history is:

  • /profile-1
  • /profile-2
  • /profile-3

The new history will push the new route, but also remove any previous entries:

  • /profile-1
  • /profile-3
  • /profile-2

dangerouslySingular can also be added to the <Link /> component to create links that enforce singular routes.

<Link href="/unique-link" dangerouslySingular /> <Link href="/unique-link" dangerouslySingular={(name) => name === 'unique-link' ? name : undefined} />

Using the imperative API

The imperative API also accepts a dangerouslySingular option.

router.navigate('/unique-link', { dangerouslySingular: true }); router.navigate('/unique-link', { dangerouslySingular: name => (name === 'unique-link' ? name : undefined), }); router.push('/unique-link', { dangerouslySingular: true }); router.push('/unique-link', { dangerouslySingular: name => (name === 'unique-link' ? name : undefined), });

The navigate and push events apply the singular constraint differently.

  • navigate: Only applies the constraint if the current route is changed
  • push: Always applies the constraint
app
_layout.tsx
index.tsx
[profile].tsx

Using the app structure above, if you have the history of:

  • /profile-1
  • /profile-2
  • /profile-2
  • /profile-3
  • /profile-2

If you navigate to /profile-2, the singular constraint will not be applied as the current route is the target route when using navigate. However, if you push /profile-2, the singular constraint will be applied.

// Will NOT apply the singular constraint as the current route is the target route router.navigate("/profile-2", { dangerouslySingular: (_, { profile } => profile === 'profile-2' ? profile : undefined ) }) // Will apply the singular constraint router.push("/profile-2", { dangerouslySingular: (_, { profile } => profile === 'profile-2' ? profile : undefined ) })