Skip to content
Skills Library
Programming · Building & coding

react-native

/react-native

Use when writing the JS/TS inside a React Native or Expo app — screens, Expo Router navigation, lists, Reanimated gestures, platform forks, offline state, native modules — or killing jank and render storms. NOT eas build/submit/OTA/config-plugin (that is `expo`), NOT a Dart app (that is `flutter`), NOT web React/DOM (that is `react` or `nextjs`).

Repository
rsc-harness ↗
Licence
MIT
Last repository change
25.10.2026

Assessment and security

100/ 100Class: good
Format25 / 25
Frontmatter with name and description, valid name, description up to 1,024 characters, name matching the folder.
Discoverability25 / 25
Description explains when to use the skill, has a reasonable length and uses third person.
Content35 / 35
At least 150 words and at most 500 lines, headings, step lists, examples or code.
Hygiene15 / 15
No placeholders such as TODO, no Windows paths, with a licence.
Riskno signals detected in the scan
Files in this package
6
Scripts
1
HTML comments
0
URLs in SKILL.md
0
Deprecation

No signals detected. Repository changed on 25.10.2026, without references to retired models or APIs.

Licence

MIT, open: use and modify while retaining author attribution.

What to check before installing
  • 1 scripts in this package. Review them before installing, as Claude may execute them.

Scripts: scripts/verify.sh

What it does and when to use it

Use when writing the JS/TS inside a React Native or Expo app — screens, Expo Router navigation, lists, Reanimated gestures, platform forks, offline state, native modules — or killing jank and render storms. NOT eas build/submit/OTA/config-plugin (that is `expo`), NOT a Dart app (that is `flutter`), NOT web React/DOM (that is `react` or `nextjs`).

Original author description

Use when writing the JS/TS inside a React Native or Expo app — screens, Expo Router navigation, lists, Reanimated gestures, platform forks, offline state, native modules — or killing jank and render storms. NOT eas build/submit/OTA/config-plugin (that is `expo`), NOT a Dart app (that is `flutter`), NOT web React/DOM (that is `react` or `nextjs`).

How it works

  1. Project shape — pick a router first
  2. Navigation
  3. Lists & performance
  4. Animation & gestures
  5. Platform-conditional code
  6. Data & state
  7. Native modules — pick the path
  8. Debugging
  9. Anti-patterns
Original SKILL.md headings · 209 lines, 1,742 words
SKILL.md preview
---
name: react-native
description: "Use when writing the JS/TS inside a React Native or Expo app — screens, Expo Router navigation, lists, Reanimated gestures, platform forks, offline state, native modules — or killing jank and render storms. NOT eas build/submit/OTA/config-plugin (that is `expo`), NOT a Dart app (that is `flutter`), NOT web React/DOM (that is `react` or `nextjs`)."
tags: [react-native, expo, mobile, ios, android, navigation, performance]
recommends: [expo, react, flutter, design, debug, performance]
origin: risco
---

# React Native (app code)

**Scope.** App code, including animation and gestures (Reanimated worklets on the UI runtime, gesture
handoff, sheets, press feedback, haptics), in bare React Native and in Expo. Visual design of web
pages and screens is `../design/SKILL.md`'s.


You write the code that runs *inside* the app: render, navigate, animate, list, persist, and author native modules. Mobile is split across two skills by **verb** — run the verb test before doing anything:

| Verb in the request | Skill |
|---|---|
| render / navigate / animate / list / persist / author-native-module / debug-runtime | **react-native** (here) |
| build / submit / update (OTA) / prebuild / config-plugin / `eas.json` / channel / runtime-version | **`../expo/SKILL.md`** |

Assume the **New Architecture is on** (Fabric + Turbo Modules). It is mandatory from RN 0.82 / Expo SDK 55 — RN 0.82 (2025-10-08) ignores any disable flag, and SDK 55 is New-Arch-only. SDK 54 (RN 0.81) was the last that could opt out. Never write `newArchEnabled: false` to dodge a bug; fix the bug. Why: the flag is a no-op on current versions, so the "fix" silently does nothing.

Reference SDK→RN map (accessed 2026-06-02): **SDK 53** = RN 0.79, **SDK 54** = RN 0.81 (React Compiler GA), **SDK 55** = RN 0.83.

## Project shape — pick a router first

This is the one real branch at scaffold time:

| Use | When |
|---|---|
| **Expo Router** (file-based) | New apps, web parity, deep links, typed routes. The recommended default. It is a layer on top of React Navigation; both are Expo-team maintained. |
| **React Navigation v7** (static API) | Brownfield apps, native integration, highly bespoke custom transitions where you need imperative control. |

Default file tree for an Expo Router app:

```text
app/                  # routes — file system IS the navigation
  (auth)/             # group: unauthenticated screens
  (app)/              # group: authenticated screens
    (tabs)/           # nested tab navigator
  _layout.tsx         # root layout + providers
components/           # dumb, reusable UI
features/<domain>/    # feature-scoped screens, hooks, components
lib/                  # clients, query setup, storage, utils
```

Keep route files thin: a route file wires params and renders a feature component. Put logic in `features/`. Why: routes get re-mounted by navigation; business logic in them is hard to test and re-runs unexpectedly.

## Navigation

Type your routes. With Expo Router, enable typed routes and let `useLocalSearchParams<{ id: string }>()` carry the contract. Why: deep links and back-stack restoration pass strings you will otherwise misread.

Gate auth at the **group layout**, declaratively — not imperatively inside an effect:

```tsx
// app/(app)/_layout.tsx — Bad: imperative nav in an effect races the first paint
export default function AppLayout() {
  const { user } = useAuth();
  useEffect(() => {
    if (!user) router.replace('/(auth)/sign-in'); // flashes protected UI first
  }, [user]);
  return <Stack />;
}
```

```tsx
// app/(app)/_layout.tsx — Good: redirect before children mount
import { Redirect, Stack } from 'expo-router';
export default function AppLayout() {
  const { user, loading } = useAuth();
  if (loading) return null;          // or a splash
  if (!user) return <Redirect href="/(auth)/sign-in" />;
  return <Stack />;
}
```

Modals/sheets: declare presentation on the screen, not by pushing a styled full-screen route — `<Stack.Screen options={{ presentation: 'modal' }} />`. Configure deep links via the `scheme` + the linking config so a cold-start link resolves to the right nested route, not just the root. A deep link landing on the wrong screen is almost always a group/segment mismatch between the URL and the `app/` tree.

## Lists & performance

Switch from `FlatList` to **FlashList** (Shopify) once a list crosses **~100 items**, has variable row heights, or renders images. Why: FlashList recycles views instead of mounting one per row, so memory and scroll jank stay flat as the list grows.

Three things every long list needs:

1. A stable `keyExtractor` — return a real id, never the index. Index keys defeat recycling and reorder rows on insert.
2. `getItemType` when rows differ structurally (header vs product vs ad). Why: it lets FlashList recycle within a type instead of remeasuring.
3. A memoized row component with **no inline closures or style objects** in `renderItem`.

```tsx
// Bad: new fn + new style object every parent render -> every visible row re-renders
<FlashList
  data={items}
  renderItem={({ item }) => (
    <Pressable style={{ padding: 12 }} onPress={() => open(item.id)}>
      <Text>{item.title}</Text>
    </Pressable>
  )}
/>
```

```tsx
// Good: stable refs, memoized row, typed rows
const Row = memo(function Row({ item }: { item: Product }) {
  const open = useOpenProduct();          // stable from context/store
  return (
    <Pressable style={styles.row} onPress={() => open(item.id)}>
      <Text>{item.title}</Text>
    </Pressable>
  );
});

<FlashList
  data={items}
  keyExtractor={(it) => it.id}
  getItemType={(it) => it.kind}
  renderItem={({ item }) => <Row item={item} />}
/>
```

FlashList caveat: rows can flash into the wrong position for a split second if mounted while entry animations run. Disable entry animations on the **initial** mount.

React Compiler (GA in SDK 54) auto-memoizes components and reduces manual `useMemo`/`useCallback` — but it does **not** remove the need for virtualization, stable keys, or `getItemType`. Don't delete your list discipline because the compiler is on. For re-render hunting and profiling, see `references/performance-debugging.md`.

## Animation & gestures

Reanimated worklets run on the **UI thread**, so animations keep going at 60fps even when JS is busy. Reanimated 4 (stable Oct 2025) requires the New Architecture / RN 0.76+ and adds CSS-style animations while keeping the worklet API. Many teams stay on Reanimated 3 for dependency compatibility — match the project's installed major.

Two rules that fix most gesture bugs:

- Wrap gesture objects in `useMemo`. Why: a new gesture object each render reattaches the recognizer and drops in-flight touches.
- Cross to JS only at the boundary with `runOnJS`. Why: calling a JS-thread function directly from a worklet crashes; everything inside the worklet must stay on the UI thread.

```tsx
// Good: memoized gesture, UI-thread shared value, JS only at the end
const x = useSharedValue(0);
const pan = useMemo(
  () =>
    Gesture.Pan()
      .onUpdate((e) => { x.value = e.translationX; })            // UI thread
      .onEnd(() => { runOnJS(onSwiped)(); x.value = withSpring(0); }), // boundary
  [onSwiped],
);
```

Use Reanimated 4 CSS animations for simple declarative cases (fades, simple transitions); use worklets for gesture-driven or physics-based motion. Never animate layout on the JS thread when a shared value will do it on the UI thread.

## Platform-conditional code

- Small forks: `Platform.select({ ios: 12, android: 8 })` or `Platform.OS === 'ios'`.
- Whole-component forks: `Button.ios.tsx` / `Button.android.tsx` — the bundler picks the right one; import `./Button` with no extension.
- Safe area: use `react-native-safe-area-context` insets, not hardcoded notch padding. Why: insets differ per device and orientation. Palette, type scale and layout intent are not decided here → `../design/SKILL.md`.
- `KeyboardAvoidingView` behaves differently per platform — `padding` on iOS, often `height` or nothing on Android; test both, don't assume the iOS behavior ports.
- Set the status bar style explicitly per screen via `expo-status-bar`.

## Data & state

- **Server state → TanStack Query.** It owns caching, dedupe, retries, and background refetch. Don't hand-roll loading flags in component state.
- **Persistence:** **MMKV** for fast synchronous key/value (auth tokens, flags, small prefs); **expo-sqlite + Drizzle** for relational or offline-first data. Pick by shape, not habit.
- Don't reach for Redux by default. Server state lives in Query; the small amount of true client state fits in context or Zustand.
- Offline-first: persist the Query cache and treat the network as an enhancement, not a precondition.

## Native modules — pick the path

| Path | When | Cost |
|---|---|---|
| **Plain JS wrapper** of an existing lib | A maintained npm package already bridges the native API | None — do this first |
| **Expo Modules API** (Swift/Kotlin) | App uses Expo; you want clean Swift/Kotlin, no codegen | Requires Expo; least boilerplate |
| **Turbo Module** (codegen from TS specs) | Bare RN, or you need the RN-default path | Codegen specs + more boilerplate |
| **Nitro Modules** | Hot path needing max throughput | Emerging; weigh maturity |

Minimal Expo Module skeleton (definition lives in the module's Swift/Kotlin):

```swift
// ios/MyModule.swift
import ExpoModulesCore
public class MyModule: Module {
  public func definition() -> ModuleDefinition {
    Name("MyModule")
    Function("hello") { (name: String) -> String in "Hi \(name)" }
  }
}
```

```ts
// index.ts — typed JS surface
import { requireNativeModule } from 'expo-modules-core';
const MyModule = requireNativeModule('MyModule');
export function hello(name: string): string { return MyModule.hello(name); }
```

Full Expo Module (Swift + Kotlin + view), the Turbo Module codegen-spec walkthrough, packaging, and autolinking → `references/native-modules.md`.

## Debugging

- Read **Hermes** stack traces from the bottom up; the JS frame that matters is usually above the native bridge frames.
- "Works on iOS, breaks on Android" (or vice versa) → run the platform-divergence checklist in `references/performance-debugging.md` before guessing.
- A red screen citing the interop layer is usually an old-architecture lib running under Fabric — update the lib or find a New-Arch-ready replacement; do **not** disable New Arch.
- A fault that resists all of the above needs systematic isolation, not more guesses → `../debug/SKILL.md`.

## Anti-patterns

| Anti-pattern | Why it hurts | Do instead |
|---|---|---|
| `FlatList` for 5k rows / image feeds | Mounts a view per row; jank + memory blowup | FlashList with `getItemType` + stable `keyExtractor` |
| Inline arrow / style object in `renderItem` | New ref every render re-renders every visible row | Memoized `Row`, `StyleSheet.create`, stable handlers |
| Gesture object built inline each render | Reattaches recognizer, drops in-flight touches | Wrap in `useMemo` keyed on real deps |
| `setState` in render path | Render loop / re-render storm on every keystroke | Derive in render or move to an event handler |
| Imperative `router.replace` in an effect to gate auth | Flashes protected UI, races first paint | `<Redirect>` at the group layout |
| `newArchEnabled: false` to dodge a New-Arch bug | No-op on RN 0.82+/SDK 55; hides the real cause | Update the offending lib; fix the bug |
| Animating layout on the JS thread | Drops frames when JS is busy | Reanimated shared value on the UI thread |
| Index as list key | Breaks recycling, reorders rows on insert | Stable domain id |
| Doing native config / `eas.json` / OTA here | Wrong skill; this is app code | `../expo/SKILL.md` |
| Hardcoded notch/safe-area padding | Wrong on other devices/orientations | `react-native-safe-area-context` insets |
Files in this package (6)
  • SKILL.md 12 KB
  • evals/README.md 1 KB
  • evals/cases.yaml 3 KB
  • references/native-modules.md 4 KB
  • references/performance-debugging.md 4 KB
  • scripts/verify.sh 5 KB

Versions

RepositoryScoreLicence
This page · ericrisco/rsc-harness100MIT
TerminalSkills/skills100Apache-2.0
nimadorostkar/Claude-Skills-collection100MIT
vercel-labs/json-render100Apache-2.0
claude-dev-suite/claude-dev-suite100MIT
lynricsy/hyperskills100MIT
jezweb/claude-skills100MIT
G1Joshi/Agent-Skills100MIT
tuannv14/claude-team-toolkit100MIT

Database snapshot: 13.11.2026 · package version 290c476a4fba (SHA-256 digest of the entire package)

Downloads in the last 30 days: 0 · ranking

Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.

Similar skills

  • 100expo
    Expo framework for React Native. Managed and bare workflows, Expo Router, EAS Build/Submit/Update, config plugins, and Expo SDK modules. USE WHEN: user mentions "Expo", "expo-router", "EAS Build", "expo-cli", "app.json", "expo-config-plugin", "Expo Go" DO NOT USE FOR: bare React…
  • 100react-native-expert
    Senior React Native and Expo engineer for building production-ready cross-platform mobile apps. Use when building React Native components, implementing navigation with Expo Router, optimizing list and scroll performance, working with animations via Reanimated, handling…
  • 100react-native-expert
    Use when building or debugging React Native apps - files using .tsx with react-native imports, app.json/app.config.ts, expo-router app/ directories, metro.config.js, eas.json, or mentions of Expo, Fabric, TurboModules, Reanimated, FlashList, or Maestro. Builds screens and…
  • 95react-native-expertise
    Use this skill when the user says 'React Native', 'Expo', 'RN app', 'FlatList performance', 'FlashList', 'Reanimated', 'React Navigation', 'Expo Router', 'EAS Build', 'EAS Update', 'OTA update', 'CodePush', 'Hermes', 'native module', 'Turbo Module', 'RN upgrade'. Builds and…
  • 90expo-react-native
    Build, debug, or review an Expo and React Native feature across iOS and Android. Use when changing navigation, native modules, permissions, app configuration, EAS builds, or platform-specific UI.
  • 90react-native-expert
    Use when building cross-platform mobile applications with React Native or Expo. Invoke for navigation patterns, platform-specific code, native modules, FlatList optimization.

Feedback and reports

Your message goes to Bartosz Sokoliński. An email address is needed only for a reply. See the privacy policy.