Communitygithub.com

Patrones de React Native / Expo

Patrones prácticos para aplicaciones React Native y Expo: navegación con Expo Router, separación de estado (servidor/cliente/ruta/formulario), obtención de datos con TanStack Query y Zod, listas eficientes, estilos con NativeWind/StyleSheet, APIs nativas y almacenamiento seguro. Úsalo al construir o editar pantallas, componentes, navegación o capas de datos en React Native/Expo.

¿Qué es Expo?

Este skill proporciona una guía completa y práctica para desarrollar aplicaciones React Native de producción con el flujo de trabajo gestionado de Expo (Expo Router, EAS, módulos `expo-*`) en la Nueva Arquitectura. Cubre los siguientes aspectos esenciales: **navegación** con validación de parámetros de ruta mediante Zod, **separación de responsabilidades del estado** (estado del servidor con TanStack Query, estado del cliente con Zustand/Jotai, estado de ruta con parámetros de Expo Router, estado de formulario con React Hook Form), **obtención de datos** con TanStack Query + Zod para validación en el límite, **renderizado de listas** con FlatList/FlashList para virtualización, **estilos** con StyleSheet o NativeWind, **APIs nativas** envueltas en hooks con limpieza de efectos, y **almacenamiento seguro** con `expo-secure-store`. También incluye antipatrones claros para evitar errores comunes como el mapeo de arrays grandes en ScrollView, la duplicación de datos del servidor en stores del cliente, el almacenamiento de tokens en AsyncStorage o la confianza en parámetros de enlaces profundos sin validar. El skill se activa al construir o editar pantallas, componentes, navegación o capas de datos en proyectos React Native/Expo, y no debe usarse para patrones web de React-DOM.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace Expo?

Patrones prácticos para construir aplicaciones React Native de producción con Expo. Cubre navegación, estado, obtención de datos, listas, estilos y APIs nativas. Se complementa con el conjunto de reglas rules/react-native/: las reglas dicen qué hacer cumplir, este skill muestra cómo.

Las bibliotecas mencionadas a continuación (NativeWind, Zustand/Jotai, TanStack Query) son opciones comunes y bien establecidas que se muestran como ilustración — los patrones importan más que el paquete específico, y cualquier equivalente funciona. Se utiliza Zod para la validación para mantener la coherencia con las reglas typescript/ existentes de ECC.

Estos patrones asumen el flujo de trabajo gestionado de Expo (Expo Router, EAS, módulos expo-*) en la Nueva Arquitectura (la opción predeterminada en los SDK de Expo recientes, obligatorio a partir del SDK 55+). NO asumen el DOM del navegador — React Native no tiene <div>, ni barra de URL, ni valores predeterminados de obtención de datos web.

Cuándo activarlo

Usa este skill cuando:

  • Construyas o edites pantallas, componentes o navegación de React Native / Expo
  • Configures el enrutamiento con Expo Router (directorio app/ basado en archivos)
  • Decidas dónde pertenece el estado (caché del servidor vs. store del cliente vs. parámetros de ruta vs. formulario)
  • Conectes la obtención de datos con TanStack Query y valides respuestas con Zod
  • Renderices listas largas o pesadas
  • Elijas o apliques un enfoque de estilos (NativeWind o StyleSheet)
  • Accedas a APIs nativas del dispositivo (cámara, ubicación, notificaciones) o almacenamiento seguro
  • Revises código RN para problemas específicos de dispositivos móviles

NO uses los patrones web/React-DOM aquí — URL-como-estado, <div> y SWR-para-navegador no se aplican a React Native.

Conceptos básicos

Estructura del proyecto (Expo Router)

Enrutamiento basado en archivos bajo app/. Mantén los archivos de ruta livianos: leen y validan parámetros, luego delegan a un componente de pantalla que vive en components/ o features/.

app/
  _layout.tsx          # stack raíz
  (tabs)/
    _layout.tsx        # navegador de pestañas
    index.tsx          # Inicio
  user/[id].tsx        # ruta dinámica
components/
features/
  user/UserProfile.tsx

Navegación: validar parámetros de ruta

Los enlaces profundos y las rutas dinámicas entregan cadenas no confiables. Valídalas con Zod antes de usarlas.

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

Estado: mantener las responsabilidades separadas

No dupliques datos del servidor en un store del cliente. Cada responsabilidad tiene su propio hogar.

ResponsabilidadOpciones comunes
Estado del servidor (datos remotos)una biblioteca de caché del servidor (TanStack Query, SWR)
Estado del cliente/UIun store liviano (Zustand, Jotai) o Context
Estado de ruta/navegaciónparámetros de Expo Router
Estado de formulariouna biblioteca de formularios (ej. React Hook Form) + validación de esquema
Secretos / tokensexpo-secure-store
Persistencia no secretaAsyncStorage / MMKV

Prefiere useState local hasta que el estado realmente necesite ser compartido.

Obtención de datos: una biblioteca de caché + Zod

Usa una biblioteca de caché del servidor (TanStack Query, SWR) en lugar de fetch-en-useEffect. Valida en el límite e infiere tipos del esquema. Maneja los estados de carga, error y vacío explícitamente. (El ejemplo usa 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] }),
  })
}

Listas: virtualizar, nunca mapear un array grande en un ScrollView

import { FlatList } from 'react-native'

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

Usa FlashList (Shopify) para listas grandes o heterogéneas.

Estilos: elegir un sistema

StyleSheet.create() es la opción nativa del framework; las bibliotecas de clases de utilidad (ej. NativeWind) son una alternativa común. Elige uno y mantén la coherencia. Nunca construyas objetos de estilo en línea en JSX en rutas críticas.

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

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

APIs nativas: envolver en hooks, limpiar efectos

Mantén las llamadas al SDK de Expo y las suscripciones dentro de hooks use*, no en JSX. Siempre limpia.

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() {
  // Rastrea el estado, no solo las coordenadas — para que la UI pueda distinguir "todavía cargando"
  // de "permiso denegado" y mostrar un mensaje procesable.
  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 }   // ignorar resultado obsoleto después de desmontar
  }, [])

  return state
}

Almacenamiento seguro para tokens

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

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

Ejemplos de código

Una pantalla completa: ruta → consulta → lista → estados

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

// Memorizado para que su referencia sea estable entre renderizados (ver la guía de listas).
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>Cargando…</Text></Centered>
  if (isError) return <Centered><Text accessibilityRole="alert">No se pudieron cargar los pedidos.</Text></Centered>
  if (!data?.length) return <Centered><Text>Aún no hay pedidos.</Text></Centered>

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

Un formulario: React Hook Form + resolución 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('Correo electrónico inválido') })
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="Dirección de correo electrónico"
          />
        )}
      />
      {errors.email && <Text accessibilityRole="alert">{errors.email.message}</Text>}
      <Button title="Guardar" onPress={handleSubmit(onSubmit)} />
    </>
  )
}

Antipatrones

// MAL: array grande mapeado dentro de un ScrollView (sin virtualización, entrecortado, alto consumo de memoria)
<ScrollView>{items.map((i) => <Row key={i.id} item={i} />)}</ScrollView>
// BIEN: FlatList / FlashList

// MAL: datos del servidor copiados en un store del cliente (dos fuentes de verdad, datos obsoletos)
const useStore = create((set) => ({ users: [], setUsers: (u) => set({ users: u }) }))
useEffect(() => { getUsers().then(setUsers) }, [])
// BIEN: useQuery posee el estado del servidor; deriva lo que necesites

// MAL: tokens en AsyncStorage (no encriptados)
await AsyncStorage.setItem('auth_token', token)
// BIEN: expo-secure-store

// MAL: confiar en parámetros de enlaces profundos sin validar
const { id } = useLocalSearchParams(); fetchUser(id)
// BIEN: validar con Zod antes de usar

// MAL: objeto de estilo en línea recreado en cada renderizado en una ruta crítica
<View style={{ padding: 16, backgroundColor: '#fff' }} />
// BIEN: StyleSheet.create en el ámbito del módulo, o className de NativeWind

// MAL: secreto real enviado en el bundle
const STRIPE_SECRET = 'sk_live_...'
// BIEN: mantener llamadas privilegiadas del lado del servidor; enviar solo claves públicas protegidas por reglas del backend

Mejores prácticas

  • Mantén los archivos de ruta livianos; coloca la lógica en componentes de pantalla y hooks use*.
  • Valida cada entrada externa (respuestas de API, parámetros de ruta, cargas push) con Zod.
  • Deja que TanStack Query posea el estado del servidor; mantén los stores del cliente pequeños.
  • Siempre renderiza estados de carga, error y vacío — nunca solo un spinner sin respaldo.
  • Virtualiza listas; memoriza renderItem; proporciona un keyExtractor estable.
  • Usa react-native-reanimated para animaciones (hilo de UI); evita trabajo pesado en el hilo de JS.
  • Almacena tokens en expo-secure-store; nunca confíes en el cliente para la autorización.
  • Respeta las áreas seguras, el Tipo Dinámico y los roles/etiquetas de accesibilidad desde el principio.
  • Confirma la compatibilidad con la Nueva Arquitectura para cada dependencia nativa antes del lanzamiento.

Skills relacionados

  • frontend-patterns — Patrones de React/Next.js (web); útil para conceptos compartidos de React, pero específicos del DOM.
  • coding-standards — Modismos de TypeScript/JavaScript que se aplican al código RN.
  • tdd-workflow, e2e-testing — Proceso de pruebas (usa Jest + React Native Testing Library, Maestro/Detox para RN).
  • security-review — Lista de verificación de seguridad general que complementa la guía de bundle/secretos de RN anterior.

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.

Skills relacionados