react-native-patterns
- Repo stars 0
- Author repo skills-registry
React Native Patterns (Expo Managed + NativeWind v5)
Purpose
Production patterns for the vendor mobile app built with React Native and Expo managed workflow. Covers list rendering performance, Reanimated animations, navigation with Expo Router, NativeWind v5 styling, native module usage, state management, and platform-specific patterns.
Core Rules
Expo Managed Workflow
- Stay within managed workflow — do not eject unless a native module is absolutely unavailable via Expo SDK or a community Expo module. Ejecting breaks OTA updates.
- Use
expo-dev-clientfor development builds with custom native modules — not bare workflow. - OTA updates via Expo Updates — keep JS bundle small. Native changes require a new build.
- App configuration in
app.json/app.config.ts. No manualInfo.plistorAndroidManifest.xmledits in managed workflow. - Target
expo@latestSDK. Pin the SDK version explicitly — do not use*.
List Rendering (CRITICAL Performance)
- Always use
FlashList(from@shopify/flash-list) instead ofFlatListorScrollViewfor any list with more than 10 items. FlashListrequiresestimatedItemSize— measure a real item, do not guess.- Memoize list items: wrap item component in
React.memo. Ensure props are stable (no inline objects/functions). - Stable callbacks: define
renderItem,keyExtractor,onEndReachedoutside the component or withuseCallback— unstable functions cause full list re-renders. - Avoid inline objects in
renderItem:renderItem={({ item }) => <Item data={item} />}is fine;renderItem={({ item }) => <Item style={{ padding: 8 }} />}re-renders every scroll. - Images in lists: use
expo-image(notImagefrom React Native) — it caches, handlesblurhash, and recycles efficiently. - Never use
ScrollView+map()for potentially long lists. It renders all items at once.
Animations (Reanimated 3)
- Use React Native Reanimated 3 (
react-native-reanimated) for all animations. - GPU-friendly properties only:
transform(translateX, translateY, scale, rotate),opacity. Never animatewidth,height,top,left,margin,padding— these trigger layout recalculation on every frame. - Derived values:
useDerivedValueto compute animation values from shared values — runs on the UI thread, not JS thread. - Gestures:
react-native-gesture-handlerwithGestureDetector. Never useonPressonAnimated.View— usePressableorGestureDetector. - Shared values:
useSharedValuefor values driven by gestures or timers.useAnimatedStyleto map shared values to style. withSpring,withTimingfor declarative animations. AvoidAnimated.loopwith Reanimated — usewithRepeat.
Navigation (Expo Router)
- Use Expo Router (file-based, built on React Navigation) — not bare React Navigation.
- Route files in
app/directory — mirrors Next.js App Router conventions. - Dynamic routes:
app/listings/[id].tsx. - Tab navigation:
app/(tabs)/_layout.tsxwith<Tabs>from Expo Router. - Stack navigation:
app/(stack)/_layout.tsxwith<Stack>. - Deep linking configured via
app.jsonschemefield — automatically handled by Expo Router. - Use native navigators (
nativeprop) — renders platform-native navigation chrome (UINavigationController on iOS, Fragment on Android). - Never use
router.back()for predictable navigation — userouter.push()orrouter.replace()with explicit paths.
NativeWind v5 Styling
- NativeWind v5 + Tailwind CSS v4 — no Babel configuration needed (Metro transformer).
- Import platform-aware components from
react-native-csswrapper:View,Text,ScrollView,Pressable,TextInput,Image,Link. classNameprop on these wrapped components — same Tailwind classes as web.- Platform-specific styles via CSS media queries:
@media (platform: ios) { ... },@media (platform: android) { ... }. - Custom theme variables in
src/global.cssvia@themeblock. - Apple semantic colours:
light-dark()function in CSS for automatic dark mode. UseplatformColor()for native system colours (e.g., iOSsystemBlue). tailwind-mergeandclsxfor conditional class composition — same as web pattern.- Never use React Native's
StyleSheet.createalongside NativeWind on the same component. Pick one approach per component.
State Management
- Zustand for global state (auth session, user preferences, offline queue).
- TanStack Query for server state — auto-caches, handles background refresh, works offline with
networkMode: 'offlineFirst'. - Local component state (
useState) for UI state that doesn't escape the component. - Never use Context for frequently-updating state — causes wide re-renders. Use Zustand.
- Offline queue: persist pending mutations in Zustand (with MMKV storage) and replay on reconnect.
Platform-Specific Code
- Platform-specific file extensions:
Component.ios.tsx/Component.android.tsxfor significantly different implementations. Platform.OS === 'ios'/Platform.OS === 'android'for minor inline differences only. Excessive inline checks are a code smell.- Safe area: always use
useSafeAreaInsets()orSafeAreaViewfromreact-native-safe-area-context. Never hardcode status bar heights. - Keyboard:
KeyboardAvoidingViewwithbehavior="padding"(iOS) /behavior="height"(Android).
Native APIs (Expo SDK)
- Camera:
expo-camera. Location:expo-location. Notifications:expo-notifications. File system:expo-file-system. - Always request permissions before accessing native APIs. Handle
deniedandundeterminedstates explicitly — show explanatory UI before the permission prompt. - Biometrics:
expo-local-authenticationfor secure local authentication. - Storage:
expo-secure-storefor sensitive data (tokens).@react-native-async-storage/async-storageor MMKV for non-sensitive persistent data.
Performance Rules
- No
console.login production — remove or gate behind__DEV__. - Avoid anonymous functions in JSX (
onPress={() => handler()}) in list items — breaksmemo. Extract to named handlers. InteractionManager.runAfterInteractionsfor expensive operations after navigation animations complete.useFocusEffectinstead ofuseEffectfor screen-level data refresh — runs only when screen is focused.
Patterns / Examples
FlashList with memoised item
import { FlashList } from '@shopify/flash-list';
import { memo, useCallback } from 'react';
const ListingItem = memo(({ item, onPress }: { item: Listing; onPress: (id: string) => void }) => (
<Pressable className="p-4 border-b border-border" onPress={() => onPress(item.id)}>
<Text className="text-base font-semibold text-foreground">{item.title}</Text>
<Text className="text-sm text-muted-foreground">{item.price}</Text>
</Pressable>
));
export function ListingsList({ listings }: { listings: Listing[] }) {
const router = useRouter();
const handlePress = useCallback((id: string) => router.push(`/listings/${id}`), [router]);
return (
<FlashList
data={listings}
renderItem={({ item }) => <ListingItem item={item} onPress={handlePress} />}
estimatedItemSize={72}
keyExtractor={item => item.id}
/>
);
}
Swipe-to-action animation (Reanimated + Gesture Handler)
import { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
export function SwipeableListItem({ children, onDelete }: Props) {
const translateX = useSharedValue(0);
const panGesture = Gesture.Pan()
.onUpdate(e => { translateX.value = Math.min(0, e.translationX); })
.onEnd(e => {
if (e.translationX < -80) { runOnJS(onDelete)(); }
else { translateX.value = withSpring(0); }
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<GestureDetector gesture={panGesture}>
<Animated.View style={animatedStyle}>{children}</Animated.View>
</GestureDetector>
);
}
NativeWind + platform colour
// src/global.css
@theme {
--color-primary: platformColor(systemBlue, #3b82f6);
--color-background: light-dark(#ffffff, #0a0a0a);
}
// Component
<View className="bg-background p-4">
<Text className="text-primary font-bold">Hello</Text>
</View>
When to Use
- Any vendor mobile app screen, component, or navigation flow
- List rendering, animations, gestures in the mobile app
- Expo SDK API usage (camera, location, notifications)
- NativeWind styling decisions
- Any mobile feature implementation or mobile story
When NOT to Use
- Customer portal (Next.js — see
nextjs-patterns) - Admin SPA (React + Vite — see
react-admin-patterns) - Web-specific patterns (CSS Grid, DOM APIs, SSR)
<!-- tomevault:4.0:skill_md:2026-05-22 -->Source: ajorobert/SpecPipeline — distributed by TomeVault.
- Fluxly category
- Engineering
- Author-declared agents
- No explicit declaration found; this is not inferred or tested compatibility
- Static check
- 88 / 100 · heuristic scan, not runtime safety proof
- Author / version / license
- @tomevault-io · no license declared
- Fluxly token estimate
- Lean
- Fluxly setup estimate
- Plug-and-play
- External API key
- No requirement detected
- Detected OS requirements
- Unspecified
- Runtime requirements
- Unspecified
- Detected file/system behavior
-
- Read-only
- Write / modify
- Detected network behavior
- Local-only
- Install commands
- None (reference only)
Profile is derived at build time from SKILL.md and install vectors. Subject to drift from author intent.
Heads up: 未限定 allowed-tools,默认拥有全部工具权限。
The current SKILL.md does not define a fixed output example. Production patterns for the vendor mobile app built with React Native and Expo managed workflow. Covers list rendering performance, Reanimated animations, navigation with Expo Router, NativeWind v5 styling, native module usage, state management, and…
Core Rules
Stay within managed workflow — do not eject unless a native module is absolutely unavailable via Expo SDK or a community Expo module. Ejecting breaks OTA updates. Use expo-dev-client for development builds with custom native modules — not bare workflow.
Always use FlashList (from @shopify/flash-list) instead of FlatList or ScrollView for any list with more than 10 items. FlashList requires estimatedItemSize — measure a real item, do not guess. Memoize list items: wrap item component in React.memo. Ensure…
Use React Native Reanimated 3 (react-native-reanimated) for all animations. GPU-friendly properties only: transform (translateX, translateY, scale, rotate), opacity. Never animate width, height, top, left, margin, padding — these trigger layout recalculation…
Use Expo Router (file-based, built on React Navigation) — not bare React Navigation. Route files in app/ directory — mirrors Next.js App Router conventions. Dynamic routes: app/listings/[id].tsx.
# React Native Patterns (Expo Managed + NativeWind v5)
## Purpose
Production patterns for the vendor mobile app built with React Native and Expo managed workflow. Covers list rendering performance, Reanimated animations, navigation with Expo Router, NativeWind v5 styling, native module usage, state management, and platform-specific patterns.
## Core Rules
### Expo Managed Workflow
* Stay within managed workflow — do not eject unless a native module is absolutely unavailable via Expo SDK or a community Expo module. Ejecting breaks OTA updates.
* Use `expo-dev-client` for development builds with custom native modules — not bare workflow.
* OTA updates via Expo Updates — keep JS bundle small. Native changes require a new build.
* App configuration in `app.json` / `app.config.ts`. No manual `Info.plist` or `AndroidManifest.xml` edits in managed workflow.
* Target `expo@latest` SDK. Pin the SDK version explicitly — do not use `*`.
### List Rendering (CRITICAL Performance)
* **Always use `FlashList`** (from `@shopify/flash-list`) instead of `FlatList` or `ScrollView` for any list with more than 10 items.
* `FlashList` requires `estimatedItemSize` — measure a real item, do not guess.
* Memoize list items: wrap item component in `React.memo`. Ensure props are stable (no inline objects/functions).
* Stable callbacks: define `renderItem`, `keyExtractor`, `onEndReached` outside the component or with `useCallback` — unstable functions cause full list re-renders.
* Avoid inline objects in `renderItem`: `renderItem={({ item }) => <Item data={item} />}` is fine; `renderItem={({ item }) => <Item style={{ padding: 8 }} />}` re-renders every scroll.
* Images in lists: use `expo-image` (not `Image` from React Native) — it caches, handles `blurhash`, and recycles efficiently.
… Author text anchors workflow facts; Fluxly only indexes current sections, terms, files, and commands.
sections -> Purpose → Core Rules → Expo Managed Workflow → List Rendering (CRITICAL Performance) → Animations (Reanimated 3) → Navigation (Expo Router)
terms -> Always use FlashList · React Native Reanimated 3 · GPU-friendly properties only · Expo Router
files/cmd -> expo-dev-client · app.json · app.config.ts · Info.plist · AndroidManifest.xml · expo@latest · FlashList · @shopify/flash-list
body sha256 -> 8ed9c55a9587
Decide Fit First
Design Intent
How To Use It
Boundaries And Review