Communitygithub.com

react-native-patterns

React Native および Expo アプリのパターン集 — Expo Router ナビゲーション、状態分離(サーバー/クライアント/ルート/フォーム)、TanStack Query + Zod によるデータ取得、効率的なリスト、NativeWind/StyleSheet スタイリング、ネイティブ API、セキュアストレージ。React Native / Expo の画面、コンポーネント、ナビゲーション、データレイヤーの構築・編集時に使用します。

react-native-patterns とは?

このスキルは、React Native と Expo を用いた本番用モバイルアプリ開発のための実践的なパターンを提供します。Expo Router によるファイルベースルーティング、TanStack Query と Zod を組み合わせた型安全なデータ取得と検証、Zustand/Jotai を用いたクライアント状態管理、FlatList/FlashList による仮想化リスト、NativeWind または StyleSheet によるスタイリング、そして expo-secure-store によるセキュアストレージの利用方法をカバーします。このスキルは、ルートファイルを薄く保ち、状態を適切に分離し、外部入力を Zod で検証し、ローディング・エラー・空状態を常に表示するといったベストプラクティスに焦点を当てています。アンチパターンとして、ScrollView 内での大きな配列のマッピング、クライアントストアへのサーバーデータの重複、AsyncStorage へのトークン保存、インラインスタイルオブジェクト、バンドル内の機密情報の埋め込みなどを明示的に示し、開発者が避けるべき落とし穴を理解できるようにしています。このスキルは、React Native (Expo) プロジェクトの構築・レビュー・リファクタリング時に最も役立ち、関連スキルとして `frontend-patterns`、`coding-standards`、`tdd-workflow`、`security-review` が挙げられています。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/react-native-patterns

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

React Native / Expo パターン

Expo で本番用 React Native アプリを構築するための実践的なパターン集。ナビゲーション、状態管理、データ取得、リスト、スタイリング、ネイティブ API をカバーします。rules/react-native/ ルールセットと組み合わせて使用します。ルールは「何を」強制するかを示し、このスキルは「どのように」実装するかを示します。

以下に記載するライブラリ(NativeWind、Zustand/Jotai、TanStack Query)は、説明用に示した一般的で確立されたオプションであり、パターン自体が特定のパッケージよりも重要です。同等の機能を持つライブラリであれば代用可能です。Zod は ECC の既存の typescript/ ルールとの一貫性を保つために検証に使用しています。

これらのパターンは、Managed Expo ワークフロー(Expo Router、EAS、expo-* モジュール)と新しいアーキテクチャ(最近の Expo SDK のデフォルト、SDK 55 以降必須)を前提としています。ブラウザの DOM を前提としていません。React Native には <div> も URL バーも Web のデフォルトのデータ取得動作もありません。

アクティブ化のタイミング

以下の場合にこのスキルを使用します:

  • React Native / Expo の画面、コンポーネント、ナビゲーションの構築または編集
  • Expo Router(ファイルベースの app/ ディレクトリ)を使ったルーティングの設定
  • 状態の配置場所の決定(サーバーキャッシュ vs クライアントストア vs ルートパラメータ vs フォーム)
  • TanStack Query を使ったデータ取得と Zod を使ったレスポンス検証
  • 長いリストや重いリストのレンダリング
  • スタイリングアプローチ(NativeWind または StyleSheet)の選択と適用
  • ネイティブデバイス API(カメラ、位置情報、通知)またはセキュアストレージへのアクセス
  • モバイル固有の問題について RN コードのレビュー

Web/React-DOM のパターン(URL を状態として使う、<div>、SWR によるブラウザ向けデータ取得など)はここでは使用しないでください。これらは React Native には適用されません。

コアコンセプト

プロジェクト構造 (Expo Router)

app/ ディレクトリによるファイルベースルーティング。ルートファイルは薄く保ちます。パラメータの読み取りと検証を行い、components/ または features/ に配置された画面コンポーネントに委譲します。

app/
  _layout.tsx          # ルートスタック
  (tabs)/
    _layout.tsx        # タブナビゲーター
    index.tsx          # ホーム
  user/[id].tsx        # 動的ルート
components/
features/
  user/UserProfile.tsx

ナビゲーション:ルートパラメータの検証

ディープリンクや動的ルートは信頼できない文字列を渡します。使用前に Zod で検証します。

// app/user/[id].tsx
import { useLocalSearchParams, router } from 'expo-router'
import { z } from 'zod'
import { UserProfile } from '@/features/user/UserProfile'

const Params = z.object({ id: z.string().uuid() })

export default function UserRoute() {
  const parsed = Params.safeParse(useLocalSearchParams())
  if (!parsed.success) {
    router.replace('/not-found')
    return null
  }
  return <UserProfile userId={parsed.data.id} />
}

状態管理:関心事を分離する

サーバーデータをクライアントストアに重複させないでください。各関心事にはそれぞれの居場所があります。

関心事一般的な選択肢
サーバー状態(リモートデータ)サーバーキャッシュライブラリ (TanStack Query, SWR)
クライアント/UI 状態軽量ストア (Zustand, Jotai) または Context
ルート/ナビゲーション状態Expo Router パラメータ
フォーム状態フォームライブラリ (例: React Hook Form) + スキーマ検証
シークレット / トークンexpo-secure-store
非機密データの永続化AsyncStorage / MMKV

状態を本当に共有する必要があるまでは、ローカルの useState を優先してください。

データ取得:キャッシュライブラリ + Zod

useEffect 内の fetch ではなく、サーバーキャッシュライブラリ(TanStack Query、SWR)を使用します。境界で検証し、スキーマから型を推論します。ローディング、エラー、空の状態を明示的に処理します。(例では TanStack Query を使用)

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { z } from 'zod'

const User = z.object({ id: z.string(), email: z.string().email() })
type User = z.infer<typeof User>

export function useUser(id: string) {
  return useQuery({
    queryKey: ['user', id],
    queryFn: async (): Promise<User> => User.parse(await api.getUser(id)),
  })
}

export function useUpdateEmail(id: string) {
  const qc = useQueryClient()
  return useMutation({
    mutationFn: (email: string) => api.updateEmail(id, email),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['user', id] }),
  })
}

リスト:仮想化、ScrollView で大きな配列を map しない

import { FlatList } from 'react-native'

<FlatList
  data={items}
  keyExtractor={(item) => item.id}
  renderItem={renderItem}          // メモ化
  initialNumToRender={10}
  windowSize={5}
/>

大規模または異種混合リストには FlashList(Shopify)を使用します。

スタイリング:一つのシステムを選ぶ

StyleSheet.create() はフレームワークネイティブのオプションです。ユーティリティクラスライブラリ(例:NativeWind)も一般的な代替案です。一つを選択し、一貫性を保ちます。ホットパスでは JSX 内にインラインでスタイルオブジェクトを構築しないでください。

// NativeWind
<View className="p-4 rounded-2xl bg-white">
  <Text className="text-base font-semibold">Hello</Text>
</View>

// StyleSheet
const styles = StyleSheet.create({ card: { padding: 16, borderRadius: 16, backgroundColor: '#fff' } })
<View style={styles.card}>...</View>

ネイティブ API:フックでラップし、エフェクトをクリーンアップ

Expo SDK の呼び出しやサブスクリプションは JSX 内ではなく、use* フック内に保持します。常にクリーンアップを行います。

import { useEffect, useState } from 'react'
import * as Location from 'expo-location'

type LocationState =
  | { status: 'loading' }
  | { status: 'denied' }
  | { status: 'granted'; coords: Location.LocationObjectCoords }

export function useCurrentLocation() {
  // 状態を追跡し、座標だけでなく「ローディング中」と「許可拒否」を
  // UI が区別してアクション可能なメッセージを表示できるようにする
  const [state, setState] = useState<LocationState>({ status: 'loading' })

  useEffect(() => {
    let active = true
    ;(async () => {
      const { status } = await Location.requestForegroundPermissionsAsync()
      if (status !== 'granted') {
        if (active) setState({ status: 'denied' })
        return
      }
      const pos = await Location.getCurrentPositionAsync({})
      if (active) setState({ status: 'granted', coords: pos.coords })
    })()
    return () => { active = false }   // アンマウント後は古い結果を無視
  }, [])

  return state
}

トークンのためのセキュアストレージ

import * as SecureStore from 'expo-secure-store'

await SecureStore.setItemAsync('auth_token', token)   // キーチェーン / キーストア
const token = await SecureStore.getItemAsync('auth_token')

コード例

フルスクリーン:ルート → クエリ → リスト → 状態

// app/(tabs)/orders.tsx
import { memo, useCallback } from 'react'
import { FlatList, Text, View } from 'react-native'
import { useQuery } from '@tanstack/react-query'
import { z } from 'zod'

const OrderSchema = z.object({ id: z.string(), total: z.number(), status: z.string() })
const OrdersSchema = z.array(OrderSchema)
type Order = z.infer<typeof OrderSchema>

function useOrders() {
  return useQuery({
    queryKey: ['orders'],
    queryFn: async () => OrdersSchema.parse(await api.listOrders()),
  })
}

// メモ化によりレンダリング間で参照が安定(リストガイダンスを参照)
const OrderRow = memo(function OrderRow({ item }: { item: Order }) {
  return (
    <View className="px-4 py-3 border-b border-neutral-200">
      <Text className="font-medium">#{item.id}</Text>
      <Text className="text-neutral-500">{item.status} · ${item.total}</Text>
    </View>
  )
})

export default function OrdersScreen() {
  const { data, isLoading, isError, refetch, isRefetching } = useOrders()
  const renderItem = useCallback(({ item }: { item: Order }) => <OrderRow item={item} />, [])

  if (isLoading) return <Centered><Text>読み込み中…</Text></Centered>
  if (isError) return <Centered><Text accessibilityRole="alert">注文を読み込めませんでした。</Text></Centered>
  if (!data?.length) return <Centered><Text>まだ注文がありません。</Text></Centered>

  return (
    <FlatList
      data={data}
      keyExtractor={(o) => o.id}
      onRefresh={refetch}
      refreshing={isRefetching}
      renderItem={renderItem}
    />
  )
}

フォーム:React Hook Form + Zod リゾルバー

import { useForm, Controller } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { TextInput, Button, Text } from 'react-native'

const Schema = z.object({ email: z.string().email('無効なメールアドレス') })
type FormValues = z.infer<typeof Schema>

export function EmailForm({ onSubmit }: { onSubmit: (v: FormValues) => void }) {
  const { control, handleSubmit, formState: { errors } } = useForm<FormValues>({
    resolver: zodResolver(Schema),
    defaultValues: { email: '' },
  })

  return (
    <>
      <Controller
        control={control}
        name="email"
        render={({ field: { value, onChange, onBlur } }) => (
          <TextInput
            value={value}
            onChangeText={onChange}
            onBlur={onBlur}
            autoCapitalize="none"
            keyboardType="email-address"
            accessibilityLabel="メールアドレス"
          />
        )}
      />
      {errors.email && <Text accessibilityRole="alert">{errors.email.message}</Text>}
      <Button title="保存" onPress={handleSubmit(onSubmit)} />
    </>
  )
}

アンチパターン

// 誤り: ScrollView 内で大きな配列を map(仮想化なし、カクつく、メモリ使用量大)
<ScrollView>{items.map((i) => <Row key={i.id} item={i} />)}</ScrollView>
// 正解: FlatList / FlashList

// 誤り: サーバーデータをクライアントストアにコピー(二重ソース、古いデータ)
const useStore = create((set) => ({ users: [], setUsers: (u) => set({ users: u }) }))
useEffect(() => { getUsers().then(setUsers) }, [])
// 正解: useQuery がサーバー状態を保持;必要なものを導出

// 誤り: AsyncStorage にトークンを保存(暗号化されていない)
await AsyncStorage.setItem('auth_token', token)
// 正解: expo-secure-store

// 誤り: ディープリンクのパラメータを信用する
const { id } = useLocalSearchParams(); fetchUser(id)
// 正解: 使用前に Zod で検証

// 誤り: ホットパスでレンダリングごとにインラインスタイルオブジェクトを再作成
<View style={{ padding: 16, backgroundColor: '#fff' }} />
// 正解: モジュールスコープで StyleSheet.create、または NativeWind className

// 誤り: バンドルに実際のシークレットを同梱
const STRIPE_SECRET = 'sk_live_...'
// 正解: 特権呼び出しはサーバーサイドに保持;公開鍵のみをバックエンドのルールで保護して出荷

ベストプラクティス

  • ルートファイルは薄く保ち、ロジックは画面コンポーネントと use* フックに配置する
  • すべての外部入力(API レスポンス、ルートパラメータ、プッシュペイロード)を Zod で検証する
  • TanStack Query にサーバー状態を管理させ、クライアントストアは小さく保つ
  • ローディング、エラー、空の状態を常にレンダリングする — フォールバックなしのスピナーだけでは不十分
  • リストを仮想化し、renderItem をメモ化し、安定した keyExtractor を提供する
  • アニメーションには react-native-reanimated(UI スレッド)を使用し、JS スレッドで重い処理を行わない
  • トークンは expo-secure-store に保存し、クライアントを認可に信頼しない
  • セーフエリア、Dynamic Type、アクセシビリティのロール/ラベルを最初から尊重する
  • リリース前にすべてのネイティブ依存関係の新しいアーキテクチャとの互換性を確認する

関連スキル

  • frontend-patterns — React/Next.js (Web) パターン;共有の React コンセプトに有用だが、DOM 固有
  • coding-standards — RN コードに適用される TypeScript/JavaScript のイディオム
  • tdd-workflow, e2e-testing — テストプロセス(Jest + React Native Testing Library、RN 向け Maestro/Detox を使用)
  • security-review — 上記の RN バンドル/シークレットガイダンスを補完する一般的なセキュリティチェックリスト

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

関連スキル