본문으로 건너뛰기
cccskills
무료GitHub에 공개한국어 소개

react-native-patterns

React Native와 Expo 앱의 화면 이동, 상태 관리, 데이터 가져오기를 설계합니다. 긴 목록 표시, 폼, 기기 기능 사용과 인증 토큰 저장 방식도 다룹니다.

원문원문 설명 보기

React Native and Expo app patterns — Expo Router navigation, state separation (server/client/route/form), TanStack Query data fetching with Zod, performant lists, NativeWind/StyleSheet styling, native APIs, and secure storage. Use when building or editing React Native / Expo screens, components, navigation, or data layers.

AI 평가90점/ 100설치 방법 보기

이럴 때 유용합니다

  • Expo 화면과 화면 이동을 만들 때
  • 가져온 데이터와 화면 상태를 정리할 때
  • 긴 목록 표시를 개선할 때
  • 입력 검증이 있는 폼을 만들 때
  • 기기 기능을 쓰고 토큰을 안전하게 저장할 때

한국어 소개

할 수 있는 일

React Native와 Expo로 모바일 앱 화면과 데이터 처리를 구현하는 설계 예시를 제공합니다. Expo Router 화면 이동, 서버 데이터와 화면 및 폼 상태의 분리, TanStack Query 데이터 가져오기, Zod 입력값 및 API 응답 검증을 다룹니다. 긴 목록 표시, 스타일 통일, 위치 정보 등 기기 기능과 인증 토큰 저장도 포함합니다.

이럴 때 유용합니다

Expo 앱에 화면을 추가하거나 상태 관리가 복잡해진 코드를 정리할 때 유용합니다. 로딩, 오류, 데이터 없음 표시를 정리하거나 긴 목록의 렌더링을 검토할 때도 활용합니다. 기기 권한이 거부된 경우의 처리 등 모바일 특유의 구현도 확인합니다.

사용 예시

  • “주문 목록과 로딩 및 오류 표시를 만들어 주세요.”
  • “화면 이동 인자와 API 응답을 Zod로 검증해 주세요.”
  • “위치 정보 처리를 훅으로 묶고 인증 토큰 저장도 검토해 주세요.”

주의 사항

Expo가 네이티브 설정을 관리하는 방식과 New Architecture를 전제로 합니다. 소개한 라이브러리는 동등한 대안으로 바꿀 수 있습니다. 배포 전 네이티브 의존성 호환성을 확인하고 토큰은 expo-secure-store에 저장합니다. 개인 키는 앱에 포함하지 않고 특권이 필요한 작업은 서버에 둡니다.

이 소개문은 공개된 SKILL.md를 바탕으로 AI(OpenAI Codex)가 작성했습니다. 정확한 사양은 아래 원문을 확인하세요.

객관 평가

AI가 SKILL.md의 내용만을 근거로 4가지 관점에서 판정했습니다. 스타 수나 인기도는 판정에 넣지 않았습니다.

지시의 명확성언제 쓰고 무엇을 하는지 알 수 있는가
A양호화면 추가, 상태 분리, 데이터 가져오기와 기기 기능 구현 등의 사용 상황을 명시합니다.
절차의 구체성절차·입출력·예시가 구체적으로 적혀 있는가
A양호폴더 구조와 Zod 검증, 목록, 위치 정보 훅, 토큰 저장 코드 예시가 있습니다.
안전성전송·삭제·명령 실행 등을 명시하고 있는가
A양호위치 정보 접근 권한 요청과 거부 처리를 제시하고 토큰은 expo-secure-store에 저장합니다.
도입의 용이성필요한 도구나 API 키가 적고 적혀 있는가
B일부 부족Expo 관리형 워크플로와 New Architecture를 전제로 하지만 사용 라이브러리의 설치 절차는 없습니다.

총점은 각 항목을 A=25점, B=15점, C=5점으로 더한 값입니다. 2026년 10월 19일 기준으로 AI(OpenAI Codex)가 판정했습니다. 참고용이므로 설치 전에 원문도 확인해 주세요.

평가가 다르다고 생각되면 재검토를 요청하기

어느 항목이 왜 다르다고 생각하는지 적어 주세요. 로그인은 필요 없습니다. SKILL.md와 대조해 평가를 다시 만들고 이곳에 반영합니다.

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

포함된 파일(1)

  • SKILL.md11.5 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

React Native / Expo Patterns

Practical patterns for building production React Native apps with Expo. Covers navigation, state, data fetching, lists, styling, and native APIs. Pairs with the rules/react-native/ ruleset: rules say what to enforce, this skill shows how.

Libraries named below (NativeWind, Zustand/Jotai, TanStack Query) are common, well-established options shown for illustration — the patterns matter more than the specific package, and any equivalent works. Zod is used for validation to stay consistent with ECC's existing typescript/ rules.

These patterns assume the managed Expo workflow (Expo Router, EAS, expo-* modules) on the New Architecture (the default in recent Expo SDKs, mandatory from SDK 55+). They do NOT assume the browser DOM — React Native has no <div>, no URL bar, and no web data-fetching defaults.

When to Activate

Use this skill when:

  • Building or editing React Native / Expo screens, components, or navigation
  • Setting up routing with Expo Router (file-based app/ directory)
  • Deciding where state belongs (server cache vs client store vs route params vs form)
  • Wiring data fetching with TanStack Query and validating responses with Zod
  • Rendering long or heavy lists
  • Choosing or applying a styling approach (NativeWind or StyleSheet)
  • Accessing native device APIs (camera, location, notifications) or secure storage
  • Reviewing RN code for mobile-specific issues

Do NOT use the web/React-DOM patterns here — URL-as-state, <div>, and SWR-for-browser do not apply to React Native.

Core Concepts

Project structure (Expo Router)

File-based routing under app/. Keep route files thin: they read and validate params, then delegate to a screen component that lives in components/ or features/.

app/
  _layout.tsx          # root stack
  (tabs)/
    _layout.tsx        # tab navigator
    index.tsx          # Home
  user/[id].tsx        # dynamic route
components/
features/
  user/UserProfile.tsx

Navigation: validate route params

Deep links and dynamic routes deliver untrusted strings. Validate them with Zod before use.

// 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} />
}

State: keep concerns separate

Do not duplicate server data into a client store. Each concern has its own home.

ConcernCommon choices
Server state (remote data)a server-cache library (TanStack Query, SWR)
Client/UI statea lightweight store (Zustand, Jotai) or Context
Route/navigation stateExpo Router params
Form statea form library (e.g. React Hook Form) + schema validation
Secrets / tokensexpo-secure-store
Non-secret persistenceAsyncStorage / MMKV

Prefer local useState until state genuinely needs sharing.

Data fetching: a cache library + Zod

Use a server-cache library (TanStack Query, SWR) instead of fetch-in-useEffect. Validate at the boundary and infer types from the schema. Handle loading, error, and empty states explicitly. (Example uses 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] }),
  })
}

Lists: virtualize, never map a big array in a ScrollView

import { FlatList } from 'react-native'

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

Use FlashList (Shopify) for large or heterogeneous lists.

Styling: pick one system

StyleSheet.create() is the framework-native option; utility-class libraries (e.g. NativeWind) are a common alternative. Choose one and stay consistent. Never build style objects inline in JSX on hot paths.

// 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>

Native APIs: wrap in hooks, clean up effects

Keep Expo SDK calls and subscriptions inside use* hooks, not in JSX. Always clean up.

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() {
  // Track status, not just coords — so the UI can tell "still loading" apart
  // from "permission denied" and show an actionable message.
  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 }   // ignore stale result after unmount
  }, [])

  return state
}

Secure storage for tokens

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

await SecureStore.setItemAsync('auth_token', token)   // Keychain / Keystore
const token = await SecureStore.getItemAsync('auth_token')

Code Examples

A full screen: route → query → list → states

// 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()),
  })
}

// Memoized so its reference is stable across renders (see the lists guidance).
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>Loading…</Text></Centered>
  if (isError) return <Centered><Text accessibilityRole="alert">Could not load orders.</Text></Centered>
  if (!data?.length) return <Centered><Text>No orders yet.</Text></Centered>

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

A form: React Hook Form + Zod resolver

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('Invalid 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="Email address"
          />
        )}
      />
      {errors.email && <Text accessibilityRole="alert">{errors.email.message}</Text>}
      <Button title="Save" onPress={handleSubmit(onSubmit)} />
    </>
  )
}

Anti-Patterns

// WRONG: large array mapped inside a ScrollView (no virtualization, janky, high memory)
<ScrollView>{items.map((i) => <Row key={i.id} item={i} />)}</ScrollView>
// RIGHT: FlatList / FlashList

// WRONG: server data copied into a client store (two sources of truth, stale data)
const useStore = create((set) => ({ users: [], setUsers: (u) => set({ users: u }) }))
useEffect(() => { getUsers().then(setUsers) }, [])
// RIGHT: useQuery owns server state; derive what you need

// WRONG: tokens in AsyncStorage (not encrypted)
await AsyncStorage.setItem('auth_token', token)
// RIGHT: expo-secure-store

// WRONG: trusting deep-link params
const { id } = useLocalSearchParams(); fetchUser(id)
// RIGHT: validate with Zod before use

// WRONG: inline style object recreated every render on a hot path
<View style={{ padding: 16, backgroundColor: '#fff' }} />
// RIGHT: StyleSheet.create at module scope, or NativeWind className

// WRONG: real secret shipped in the bundle
const STRIPE_SECRET = 'sk_live_...'
// RIGHT: keep privileged calls server-side; ship only public keys protected by backend rules

Best Practices

  • Keep route files thin; put logic in screen components and use* hooks.
  • Validate every external input (API responses, route params, push payloads) with Zod.
  • Let TanStack Query own server state; keep client stores small.
  • Always render loading, error, and empty states — never just a spinner with no fallback.
  • Virtualize lists; memoize renderItem; provide a stable keyExtractor.
  • Use react-native-reanimated for animation (UI thread); avoid heavy work on the JS thread.
  • Store tokens in expo-secure-store; never trust the client for authorization.
  • Respect safe areas, Dynamic Type, and accessibility roles/labels from the start.
  • Confirm New Architecture compatibility for every native dependency before release.

Related Skills

  • frontend-patterns — React/Next.js (web) patterns; useful for shared React concepts, but DOM-specific.
  • coding-standards — TypeScript/JavaScript idioms that apply to RN code.
  • tdd-workflow, e2e-testing — testing process (use Jest + React Native Testing Library, Maestro/Detox for RN).
  • security-review — general security checklist that complements the RN bundle/secret guidance above.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Design and audit interfaces for Web, iOS, and Android using WCAG 2.2 accessibility guidance. Check screen-reader labels, keyboard navigation, contrast, and control sizes to identify barriers and guide implementation for people using assistive technology.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Adding descriptive labels to icon-only buttons
  • Reviewing keyboard navigation and modal focus
  • Checking contrast and control sizes
affaan-m/ECC28.3만2026년 10월 16일 업데이트

Investigate unreliable AI agents across 12 layers, including instructions, memory, tool execution, and display. Use code and logs to trace failures, rank findings by severity, and produce an ordered repair plan that prioritizes code changes.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Investigating failures that appear only inside an agent application
  • Tracing old conversation content leaking into new sessions
  • Checking skipped or falsely claimed tool execution
affaan-m/ECC28.3만2026년 10월 16일 업데이트

agent-eval

무료요약 있음AI 평가 80점

실제 개발 과제로 여러 코딩 에이전트를 비교합니다. 성공률, 확인 가능한 API 비용, 소요 시간과 반복 실행의 안정성을 측정해 도입 결정과 업데이트 후 평가에 활용합니다.

  • 실제 개발 과제로 에이전트를 비교합니다
  • 새 도구나 모델을 도입하기 전에 평가합니다
  • 에이전트 업데이트 후 성능을 확인합니다
affaan-m/ECC28.3만2026년 10월 16일 업데이트

Design and refine an AI agent’s available actions, tool inputs, and result formats. Define recovery steps for errors, manage the information kept in context, and evaluate changes through completion rates, retries, and cost per successful task.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Define an AI agent’s tool set and input formats
  • Make tool results and next steps easier to interpret
  • Specify safe retries and stopping conditions
affaan-m/ECC28.3만2026년 10월 16일 업데이트

agent-introspection-debugging

무료요약 있음AI 평가 100점

AI 에이전트의 반복 실패와 도구 사용을 기록하고 원인을 좁힙니다. 영향이 제한된 복구 작업과 결과 보고를 통해 근거 있는 재시도를 돕습니다.

  • 같은 도구 작업을 반복하는 원인 조사
  • 대화 증가에 따른 출력 품질 저하 조사
  • 파일 경로와 환경 불일치 확인
affaan-m/ECC28.3만2026년 10월 16일 업데이트

Diagnose AI agent runs that fail repeatedly, loop through tools, or lose track of the task. Capture errors and environment details, test a focused recovery action, and produce a report explaining the evidence and outcome.

한국어 요약은 준비 중입니다. 영어 요약을 표시하고 있습니다.

  • Diagnosing repeated failures in an agent run
  • Investigating loops through the same tool calls
  • Reviewing output degradation as context grows
affaan-m/ECC28.3만2026년 10월 5일 업데이트

affaan-m의 스킬 모두 보기

이 스킬의 문제 신고