rules/react-native/patterns.md
This file extends common/patterns.md with React Native / Expo specific patterns. Note: Do NOT install the
web/ruleset in a React Native project — those patterns assume the DOM (e.g. URL-as-state) and do not apply here.
Expo Router is Expo's built-in, file-based router (app/ directory); React Navigation is the established alternative. The examples below use Expo Router; the principles apply either way.
app/**) thin — they wire params + hooks to a screen component that lives in components/ or features/.useLocalSearchParams, Link, router.push).// app/user/[id].tsx
import { useLocalSearchParams, router } from 'expo-router'
import { z } from 'zod'
const Params = z.object({ id: z.string().uuid() })
export default function UserScreen() {
// Use safeParse, not parse: a malformed deep link would otherwise throw
// during render and crash the screen. Redirect instead of throwing.
const parsed = Params.safeParse(useLocalSearchParams())
if (!parsed.success) {
router.replace('/not-found')
return null
}
return <UserProfile userId={parsed.data.id} />
}
The rule is to keep these concerns separate and not duplicate server data into client stores. The tools listed are common choices, not requirements — pick what fits your project.
| Concern | Common choices |
|---|---|
| Server state | a server-cache library (TanStack Query, SWR) |
| Client/UI state | a lightweight store (Zustand, Jotai) or Context |
| Navigation/route state | Expo Router params (NOT a global store) |
| Form state | a form library (e.g. React Hook Form) with schema validation |
| Secure persistence | expo-secure-store |
| Non-secure persistence | AsyncStorage / MMKV |
useState until sharing is actually needed.Use a server-cache library (TanStack Query, SWR) instead of ad-hoc fetch-in-useEffect. The examples use TanStack Query.
useQuery) and mutations through it (e.g. useMutation) with cache invalidation.typescript/ rules.)function useUser(id: string) {
return useQuery({
queryKey: ['user', id],
queryFn: async () => userSchema.parse(await api.getUser(id)),
})
}
FlatList/SectionList (or FlashList for large/heavy lists) — never .map() a large array inside a ScrollView.keyExtractor; memoize renderItem.use* hooks.