react-native-patterns
React Native patterns: navigation (Expo Router), platform-specific code, local storage, push notifications (Expo), performance optimization, network handling, and bridging native APIs. For Expo-based React Native apps.
Best use case
react-native-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
React Native patterns: navigation (Expo Router), platform-specific code, local storage, push notifications (Expo), performance optimization, network handling, and bridging native APIs. For Expo-based React Native apps.
Teams using react-native-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/react-native-patterns/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How react-native-patterns Compares
| Feature / Agent | react-native-patterns | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
React Native patterns: navigation (Expo Router), platform-specific code, local storage, push notifications (Expo), performance optimization, network handling, and bridging native APIs. For Expo-based React Native apps.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
SKILL.md Source
# React Native Patterns Skill
## When to Activate
- Building a cross-platform mobile app (iOS + Android)
- Adding navigation, deep links, or tab structure
- Persisting data locally on device
- Implementing push notifications
- Debugging performance issues (re-renders, list scrolling)
- Handling offline / flaky network conditions
- Choosing the right local storage option between SecureStore, MMKV, and SQLite based on data type and sensitivity
- Optimizing FlatList rendering with `getItemLayout`, `removeClippedSubviews`, and memoized callbacks
- Setting up Expo Router file-based navigation and dynamic route segments
---
## Stack Choice
**Recommended:** Expo (managed workflow) for most apps.
- Faster setup, OTA updates, built-in APIs (camera, notifications, location)
- Use bare workflow only if you need custom native modules Expo doesn't support
```bash
npx create-expo-app@latest MyApp --template
# Choose: TypeScript + Expo Router (file-based routing, like Next.js)
```
---
## Navigation with Expo Router
Expo Router uses the filesystem as the route definition — same mental model as Next.js App Router.
```
app/
_layout.tsx # Root layout (global providers)
(tabs)/
_layout.tsx # Tab bar layout
index.tsx # / (Home tab)
explore.tsx # /explore (Explore tab)
product/
[id].tsx # /product/:id (dynamic segment)
modal.tsx # Presented as modal
```
```tsx
// app/(tabs)/_layout.tsx — Tab navigator
import { Tabs } from 'expo-router';
import { Home, Compass, User } from 'lucide-react-native';
export default function TabLayout() {
return (
<Tabs screenOptions={{ tabBarActiveTintColor: '#3b82f6' }}>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color, size }) => <Home color={color} size={size} />,
}}
/>
<Tabs.Screen
name="explore"
options={{
title: 'Explore',
tabBarIcon: ({ color, size }) => <Compass color={color} size={size} />,
}}
/>
</Tabs>
);
}
// Navigation from any component
import { router, Link } from 'expo-router';
// Programmatic navigation
router.push('/product/123');
router.replace('/(tabs)/'); // Replace current screen (no back)
router.back();
// Link component
<Link href="/product/123">View product</Link>
// Dynamic route — app/product/[id].tsx
import { useLocalSearchParams } from 'expo-router';
export default function ProductScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const { data } = useProduct(id);
// ...
}
```
---
## Platform-Specific Code
```tsx
import { Platform, StyleSheet } from 'react-native';
// Option 1: Platform.select
const styles = StyleSheet.create({
container: {
paddingTop: Platform.select({
ios: 44, // iOS status bar height
android: 24,
default: 0,
}),
},
});
// Option 2: Platform.OS check
const shadowStyle = Platform.OS === 'ios'
? {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
}
: {
elevation: 4, // Android equivalent of iOS shadow
};
// Option 3: Platform-specific files (best for large differences)
// Button.ios.tsx — iOS version
// Button.android.tsx — Android version
// RN automatically picks the right file based on platform
```
---
## Local Storage
```typescript
// For simple key/value: expo-secure-store (encrypted, for tokens)
import * as SecureStore from 'expo-secure-store';
await SecureStore.setItemAsync('auth_token', token);
const token = await SecureStore.getItemAsync('auth_token');
await SecureStore.deleteItemAsync('auth_token');
// For larger structured data: MMKV (fastest, synchronous)
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
storage.set('user.id', '123');
storage.set('settings', JSON.stringify({ theme: 'dark' }));
const settings = JSON.parse(storage.getString('settings') ?? '{}');
// For relational data: expo-sqlite
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabaseSync('app.db');
db.runSync(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
done INTEGER DEFAULT 0
)
`);
const todos = db.getAllSync<{ id: number; text: string; done: number }>(
'SELECT * FROM todos WHERE done = 0'
);
```
---
## Push Notifications (Expo)
```typescript
// notifications.ts
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
// Configure foreground behavior
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
async function registerForPushNotifications(): Promise<string | null> {
if (!Device.isDevice) {
console.warn('Push notifications require a physical device');
return null;
}
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') return null;
// Get Expo push token (for Expo's notification service)
const token = await Notifications.getExpoPushTokenAsync({
projectId: process.env.EXPO_PUBLIC_PROJECT_ID,
});
// Save token to backend
await api.post('/users/push-token', { token: token.data });
return token.data;
}
// Send from backend (using Expo Push API)
async function sendPushNotification(expoPushToken: string, title: string, body: string) {
await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: expoPushToken,
title,
body,
data: { screen: 'notifications' }, // Handled in app for deep link
}),
});
}
```
---
## Performance: Lists
```tsx
// FlatList: virtual — only renders visible items
import { FlatList, View, Text } from 'react-native';
function ProductList({ products }: { products: Product[] }) {
const renderItem = useCallback(({ item }: { item: Product }) => (
<ProductCard product={item} />
), []);
const keyExtractor = useCallback((item: Product) => item.id, []);
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={keyExtractor}
// Performance props
removeClippedSubviews={true} // Unmount off-screen items
maxToRenderPerBatch={10} // Items rendered per batch
windowSize={10} // Render window size in viewport units
initialNumToRender={8} // Items visible on first paint
getItemLayout={(data, index) => (
// If item height is fixed — skips layout measurement (much faster)
{ length: 80, offset: 80 * index, index }
)}
// Infinite scroll
onEndReachedThreshold={0.5}
onEndReached={fetchNextPage}
ListFooterComponent={isFetchingNextPage ? <ActivityIndicator /> : null}
/>
);
}
```
---
## Network Handling
```typescript
// Offline detection
import NetInfo from '@react-native-community/netinfo';
function useNetworkStatus() {
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected ?? true);
});
return unsubscribe;
}, []);
return isConnected;
}
// TanStack Query: retry on reconnect
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 3,
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 10000),
// Refetch when app comes back to foreground
refetchOnWindowFocus: true,
// Refetch when network reconnects
refetchOnReconnect: true,
},
},
});
```
---
## App Configuration (app.json / app.config.ts)
```typescript
// app.config.ts — dynamic config with env vars
export default {
expo: {
name: 'MyApp',
slug: 'myapp',
version: '1.0.0',
orientation: 'portrait',
icon: './assets/icon.png',
splash: { image: './assets/splash.png', backgroundColor: '#ffffff' },
ios: {
bundleIdentifier: 'com.company.myapp',
supportsTablet: true,
infoPlist: {
NSCameraUsageDescription: 'Used for profile photo',
},
},
android: {
package: 'com.company.myapp',
permissions: ['CAMERA'],
},
extra: {
apiUrl: process.env.API_URL,
eas: { projectId: process.env.EAS_PROJECT_ID },
},
plugins: [
'expo-router',
'expo-secure-store',
['expo-notifications', { color: '#3b82f6' }],
],
},
};
```
---
## Checklist
- [ ] Expo Router used for file-based navigation
- [ ] Sensitive data (tokens, keys) stored in `expo-secure-store`, not AsyncStorage
- [ ] `FlatList` used for all lists (never `ScrollView` + map for long lists)
- [ ] `keyExtractor` and `renderItem` memoized with `useCallback`
- [ ] `getItemLayout` set for fixed-height list items
- [ ] Platform differences handled with `Platform.select()` or `.ios.tsx`/`.android.tsx`
- [ ] Push notification token saved to backend on permission grant
- [ ] Network connectivity checked before offline-sensitive operations
- [ ] OTA updates configured via EAS Update for post-release patches
- [ ] Deep links configured in `app.config.ts` scheme/intentFiltersRelated Skills
zero-trust-patterns
Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.
webrtc-patterns
WebRTC patterns — peer connection setup, ICE/STUN/TURN configuration, signaling server design, SFU vs mesh topology, screen sharing, media track management, and reconnect/ICE restart handling.
webhook-patterns
Webhook patterns for receiving, verifying (HMAC), and idempotently processing third-party events. Covers Stripe, GitHub, and generic webhook patterns, delivery guarantees, retry handling, and testing.
wasm-patterns
WebAssembly patterns: wasm-pack, wasm-bindgen (JS↔Wasm interop), WASI, Component Model, wasm-opt, Rust-to-WASM compilation, JS integration (web workers, streaming instantiation), and production deployment (CDN, Content-Type headers).
ux-micro-patterns
UX micro-patterns for every product state: Empty States, Loading States (skeleton screens, spinners, optimistic UI), Error States, Success States, Confirmation Dialogs, Onboarding Flows, and Progressive Disclosure. These patterns apply to every feature — done wrong, they're the biggest source of user confusion.
typescript-patterns
TypeScript patterns — type system best practices, strict mode, utility types, generics, discriminated unions, error handling with Result types, and module organization. Core patterns for production TypeScript.
typescript-patterns-advanced
Advanced TypeScript — mapped types, template literal types, conditional types, infer, type guards, decorators, async patterns, testing with Vitest/Jest, and performance. Extends typescript-patterns.
typescript-monorepo-patterns
TypeScript monorepo patterns with Turborepo + pnpm workspaces. Covers package structure, shared configs, task pipeline caching, build orchestration, and publishing strategy.
terraform-patterns
Infrastructure as Code with Terraform — project structure, remote state, modules, workspace strategy, AWS/GCP patterns, CI/CD integration, and security hardening. The standard for managing production infrastructure.
swiftui-patterns
SwiftUI architecture patterns, state management with @Observable, view composition, navigation, performance optimization, and modern iOS/macOS UI best practices.
swift-patterns
Core Swift patterns — value vs reference types, protocols, generics, optionals, Result, error handling, Codable, and module organization. Foundation for all Swift development.
swift-patterns-advanced
Advanced Swift patterns — property wrappers, result builders, Combine basics, opaque & existential types, macro system, advanced generics, and performance optimization. Extends swift-patterns.