Skip to content

Passing Objects Using Expo Router

Problem Statement

When building mobile apps with Expo Router, a common requirement is passing complex data between screens. Specifically, developers need to send JavaScript objects (like product or user data) to detail screens without making redundant API calls.

The challenge arises because Expo Router requires navigation state to be serialized into URL parameters, which causes problems:

  • Passing entire objects leads to URL encoding issues with special characters
  • Complex objects with URLs or nested structures cause route errors
  • Data types like Dates convert to strings
  • Security risks exposing sensitive data in URLs
  • Potential URL length limitations (~2000 characters)

The optimal solution uses global state instead of URL parameters. Pass only an identifier via the route path, then retrieve the full object from application state.

Implementation Steps

  1. Create a global state context:
ts
import React, { createContext, useState, useContext } from 'react';

const ItemCacheContext = createContext<{
  cache: Record<string, any>;
  addItem: (item: any) => void;
}>({
  cache: {},
  addItem: () => {},
});

export function ItemCacheProvider({ children }: { children: React.ReactNode }) {
  const [cache, setCache] = useState<Record<string, any>>({});
  
  const addItem = (item: any) => {
    setCache(prev => ({ ...prev, [item.id]: item }));
  };
  
  return (
    <ItemCacheContext.Provider value={{ cache, addItem }}>
      {children}
    </ItemCacheContext.Provider>
  );
}

export function useItemCache() {
  return useContext(ItemCacheContext);
}
  1. Modify your navigation logic:
tsx
import { router } from 'expo-router';
import { useItemCache } from '../context/ItemCache';

export default function ListScreen() {
  const { addItem } = useItemCache();
  
  const handlePress = (item) => {
    addItem(item);
    router.push(`/details/${item.id}`);
  };
  
  return (
    <FlatList
      data={items}
      renderItem={({ item }) => (
        <TouchableOpacity onPress={() => handlePress(item)}>
          <Text>{item.title}</Text>
        </TouchableOpacity>
      )}
    />
  );
}
  1. Retrieve in your detail screen:
tsx
import { useLocalSearchParams } from 'expo-router';
import { useItemCache } from '../../context/ItemCache';

export default function ItemDetail() {
  const { id } = useLocalSearchParams();
  const { cache } = useItemCache();
  
  const item = cache[id as string];
  
  if (!item) {
    return <Text>Item not found</Text>;
  }
  
  return (
    <View>
      <Text>{item.title}</Text>
      <Image source={{ uri: item.imageURL }} style={{ width: 200, height: 200 }} />
    </View>
  );
}

Tip

Wrap your app in the provider:

tsx
import { ItemCacheProvider } from './context/ItemCache';

export default function App() {
  return (
    <ItemCacheProvider>
      <Stack />
    </ItemCacheProvider>
  );
}

Key Advantages

  • Avoids URL encoding issues and limitations
  • Maintains object integrity (dates, nested objects, etc.)
  • No additional network requests required
  • Keeps URL clean and RESTful
  • Improves performance for large datasets
  • Follows React Navigation best practices

Alternative Approaches

URL Parameters (Simple Primitives Only)

For single primitive values:

ts
router.push(`/details/${item.id}?name=${encodeURIComponent(item.name)}`);
ts
const { id, name } = useLocalSearchParams();

Base64 Serialization (Small Objects Only)

Warning

Use only for small, non-sensitive objects due to URL length limitations.

ts
import base64 from 'base-64';

const serializedItem = base64.encode(JSON.stringify(item));
router.push(`/details/${serializedItem}`);
ts
const { data } = useLocalSearchParams<{ data: string }>();
const item = JSON.parse(base64.decode(data));

Best Practices Summary

  1. Preferred:

    • Pass object identifiers (IDs)
    • Use context API, Zustand, or React Query
    • Implement caching mechanisms
  2. Avoid:

    • Passing large objects via URLs
    • Putting sensitive data in URLs
    • Using JSON.stringify without Base64 encoding
    • Complex nested objects as params