react-native-advanced
React Native and Expo patterns for navigation, data fetching lifecycle, infinite scroll lists, form handling, state persistence, authentication routing, gesture-driven animations, bottom sheets, push notifications, and OTA updates. Use when building Expo/React Native apps that need data prefetching without route loaders, auth guard routing, infinite scroll with FlashList, gesture-driven animations, or native platform integration (push notifications, OTA updates, MMKV persistence). Do not use for React web apps.
What this skill does
# React Native Advanced: Expo + TanStack/XState/Zustand Ecosystem
React Native and Expo patterns for apps built with the TanStack ecosystem, XState, and
Zustand. This skill extends `react-advanced` (core cross-platform patterns). Read that skill
first for React Query, XState, Zustand, Zod, TanStack Form, and TanStack Table conventions.
---
## RN Architecture
Libraries map differently in React Native compared to web:
| Web Library | RN Equivalent | Key Difference |
| ---------------- | ------------------ | ------------------------------------------------- |
| TanStack Router | Expo Router | No route loaders on native, file-based navigation |
| TanStack Start | — | No SSR/server functions on native |
| TanStack Virtual | FlashList | Native view recycling, not DOM virtualization |
| localStorage | MMKV | Synchronous, native-thread, 30x faster |
| window events | AppState/NetInfo | Manual wiring required for focus/online managers |
| CSS animations | Reanimated | UI-thread worklets, CSS transitions (v4) |
| DOM events | Gesture Handler | Gesture composition API, UI-thread callbacks |
| `<img>` | expo-image | SDWebImage/Glide, blurhash, disk caching |
| Web Push API | expo-notifications | FCM/APNs, channels, background tasks |
| Service Workers | expo-updates | OTA updates, staged rollout, emergency rollback |
Cross-platform libraries (identical API on web and RN):
React Query, XState, Zustand, Zod, TanStack Form, TanStack Table
---
## Required Setup
Two integrations are **mandatory** — without them, React Query's auto-refetch and offline
handling do not work in React Native:
- **focusManager** — wire `AppState` to `focusManager.setFocused()` so `refetchOnWindowFocus`
works when the app returns to foreground. Without this, the default is silently ignored.
- **onlineManager** — wire `NetInfo` to `onlineManager.setOnline()` so queries pause/resume
on connectivity changes.
Call both hooks once in the root `_layout.tsx` inside `QueryClientProvider`.
See `references/react-query-rn.md` for full implementation.
---
## Data Fetching Without Route Loaders
Expo Router has **no native route loaders** (data loaders are web-only/alpha). The pattern
is: prefetch on user interaction, consume in the destination screen.
Rules:
- Fire `prefetchQuery` without `await` before `router.push` — awaiting makes navigation slow.
- Use `useFocusEffect` (from `expo-router`) to invalidate stale data when a screen regains
focus. `useEffect` does not re-run when navigating back because screens stay mounted.
- Use `invalidateQueries` (respects `staleTime`) instead of `refetch()` (always re-fetches).
See `references/react-query-rn.md` for prefetch and `useRefreshOnFocus` patterns.
---
## Navigation + Auth
### Stack.Protected (Expo Router v5+, recommended)
```typescript
// app/_layout.tsx
export default function RootLayout() {
const session = useAuthStore((s) => s.session)
return (
<Stack>
<Stack.Protected guard={!!session}>
<Stack.Screen name="(tabs)" />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack.Protected>
<Stack.Protected guard={!session}>
<Stack.Screen name="sign-in" />
</Stack.Protected>
</Stack>
)
}
```
When `session` flips, Expo Router automatically redirects and cleans history. For complex
auth flows (token check, refresh, error recovery), use XState to manage auth state and
derive a boolean for the `guard` prop.
See `references/expo-router.md` for the XState auth variant and Zustand auth store patterns.
---
## Lists: FlashList + React Query
FlashList replaces TanStack Virtual for RN — it uses native view recycling instead of
DOM-based absolute positioning.
Rules:
- The `!isFetchingNextPage` guard in `onEndReached` is essential — FlashList can fire
`onEndReached` multiple times in quick succession, causing duplicate fetches.
- Memoize the flattened `items` array with `useMemo` — `data.pages.flatMap()` creates a new
reference each render.
- Stabilize `handleEndReached` with `useCallback`.
- `getNextPageParam` must return `undefined` (not `null`) to signal no next page.
See `references/lists.md` for the full infinite scroll pattern, FlashList v2 changes,
and performance tuning.
---
## TanStack Form in RN
TanStack Form is headless — no DOM dependency, no adapter needed. Key differences from web:
- `TextInput` uses `onChangeText` (string directly) instead of `onChange` (event object).
Wire `field.handleChange` directly to `onChangeText`.
- For numeric fields, convert at the call site:
`onChangeText={(val) => field.handleChange(val === '' ? null : Number(val))}`.
- Wrap forms in `ScrollView` with `keyboardShouldPersistTaps="handled"` — otherwise the
first tap on Submit dismisses the keyboard instead of firing the press.
---
## Zustand Persist with MMKV
MMKV is synchronous and runs on the native thread — no async hydration gap. Create the
MMKV instance at module level (never inside a component). The `StateStorage` adapter must
return `null` for missing keys: `mmkv.getString(name) ?? null`.
For sensitive data, use an encrypted MMKV instance with `encryptionKey`. For the hybrid
pattern (hardware-backed key + encrypted MMKV), see `references/expo-essentials.md`.
See `references/zustand-rn.md` for the full adapter, store creation, encrypted storage,
hydration timing, and vanilla store patterns.
---
## Animations & Gestures
Reanimated runs animations on the **UI thread** via worklets. Gesture Handler routes touch
events to the same thread. Reanimated 4 adds CSS-style declarative transitions.
### Critical Rules
- **Replace entire values**: `sv.value = { x: 50 }` not `sv.value.x = 50` (breaks reactivity)
- **No destructuring**: `const { x } = sv.value` creates a plain number, not reactive
- **No reads during render**: shared value reads are side effects, violate Rules of React
- **Shared values don't re-render**: if you need React to respond, maintain separate state
- **GestureHandlerRootView** must wrap the app root with `style={{ flex: 1 }}`
- **Android modals** need their own `GestureHandlerRootView` (outside native root view)
### Threading Summary
Shared values live on the UI thread. Reading `.value` on the JS thread is a blocking bridge
call — never do it in hot paths. Gesture callbacks and `useAnimatedStyle` run on the UI
thread. Use `runOnJS` (Reanimated 3) or `scheduleOnRN` (Reanimated 4) to call JS functions.
### Declarative vs Imperative
Use **CSS transitions** (Reanimated 4) for state-driven style changes (toggle colors,
opacity, dimensions). Use **layout animations** (`entering`/`exiting` props) for mount/unmount.
Use **worklets + shared values** for gesture-driven animations and imperative chains.
See `references/animations.md` for detailed patterns, Reanimated 4 API changes, gesture
composition, and CSS transitions.
---
## Bottom Sheets
`@lodev09/react-native-true-sheet` — a native bottom sheet backed by
`UISheetPresentationController` (iOS) and `BottomSheetDialog` (Android). No Reanimated
dependency. New Architecture only.
### Key Rules
- **Imperative control only** — use `ref.present()`/`dismiss()`/`resize()`, not state props.
- **Max 3 detents**, sorted smallest to largest. Use `'auto'` for content-fitting.
- **Never combine `scrollable` with `'auto'` detent** — they conflict. Use fixed detents.
- **Never use `flex: 1` on sheet content** — collapses to zero height. Use `flexGrow` or
fixed height.
- **Always `dismiss()` before unmounting** — the native sheet outlives the React component.
- **Standard `TextInput` works** — no special keyboard component needed (native handling).
- **Standard `ScrollView`/`FlashList` works** with `scrollable` + `nestedScrollEnabled` — auto-detected in v3 (up to 2 leRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.