Un flujo de decisiones paso a paso para estructurar componentes de React. Recorre cada pregunta en orden: tus respuestas determinan la arquitectura, los patrones y las herramientas que necesitas antes de escribir una sola línea de código.
Empieza en el Paso 1 y recorre cada decisión de forma secuencial. Cada paso se apoya en el anterior. Al final, tendrás un plano claro para tu componente: qué renderiza, cómo gestiona el estado, de dónde vienen los datos y cómo se comunica con el resto de tu aplicación.
Mantenlo simple: un Server Component sin hooks ni estado
Pasa al Paso 2
¿Renderiza una lista de elementos?
Necesitas .map(), props key únicas y probablemente datos de un padre o de una API
Considera un componente de un solo elemento
¿Renderiza otros componentes (composición)?
Diseña ahora la API basada en children o en slots
Es un componente hoja: enfócate en las props
¿A veces no renderiza nada (condicional)?
Planifica tu estrategia de renderizado condicional (ternario, &&, retorno temprano)
Siempre renderiza: camino más sencillo
Decisión clave: Si el componente solo renderiza contenido estático sin interactividad, debe ser un Server Component (el valor por defecto en el App Router de Next.js). Detente aquí: no necesitas JavaScript del lado del cliente.
Relacionado:Componentes -- patrones de composición, props, children | JSX y TSX -- reglas de expresiones, fragments, compilación de JSX | Renderizado condicional -- patrones de ternario, &&, retorno temprano | Server Components -- cuándo y por qué usarlos
Si el usuario hace clic, escribe, pasa el cursor por encima, arrastra o interactúa de cualquier otra forma con él, es un Client Component.
Tipo de interacción
Qué necesitas
Ejemplo
Clics de botón / toggles
Manejador onClick, posiblemente useState
Me gusta/quitar me gusta, expandir/colapsar
Entrada de texto
Input controlado con useState + onChange
Barra de búsqueda, campo de formulario
Efectos de hover / focus
Solo CSS (preferido) o onMouseEnter/onFocus
Tooltip, disparador de menú desplegable
Arrastrar y soltar
Librería de terceros (dnd-kit) o Drag API
Lista ordenable, tablero kanban
Atajos de teclado
useEffect + addEventListener o hook personalizado
Búsqueda con Cmd+K, Escape para cerrar
Decisión clave: Añade "use client" al principio del archivo solo cuando el componente realmente necesite APIs del navegador, hooks o manejadores de eventos. Mantén el límite del cliente lo más bajo posible en el árbol.
Determina qué datos gestiona el componente internamente frente a los que vienen de fuera.
Escenario de estado
Enfoque recomendado
Sin estado: pura visualización
Solo props. Los componentes sin estado son más fáciles de probar y reutilizar.
Toggle/contador simple (1-2 valores)
useState: una llamada por cada valor independiente
Campos relacionados que cambian juntos (formulario con más de 3 campos)
Un único useState con un objeto, useReducer para transiciones complejas, o un store de Zustand si el mismo estado puede necesitar leerse/escribirse desde componentes hermanos
Máquina de estados compleja (asistente de varios pasos, estado de arrastre)
useReducer con acciones explícitas, una librería de máquina de estados, o un store de Zustand con métodos de acción cuando los pasos abarcan varios componentes
Valores derivados (lista filtrada, total calculado)
Calcúlalos durante el render: no los almacenes en un estado aparte
Decisión clave: Si estás recurriendo a más de 3-4 llamadas a useState en un mismo componente, detente y considera useReducer o extraer un hook personalizado.
Relacionado:useState -- funciones actualizadoras, inicialización perezosa, batching | useReducer -- estado basado en acciones para lógica compleja | Configuración de Zustand -- store ligero para estado entre componentes | Tipar el estado -- tipar formas de estado complejas | Máquinas de estados -- patrones de estado finito para la UI
La estrategia de obtención de datos depende de si estás en un Server Component o en un Client Component.
Fuente de datos
Mejor enfoque
Evita
Base de datos / ORM
Obtenerlos directamente en un Server Component asíncrono
Exponer consultas a la BD en código del cliente
Tu propia API
Server Action o fetch en un Server Component
useEffect + fetch en un Client Component (provoca cascadas)
API de terceros
Fetch en Server Component, o SWR/TanStack Query para el lado del cliente
useEffect + fetch sin caché
Parámetros de URL / search params
useParams() / useSearchParams() en un Client Component, o la prop params en un Server Component
Parsear window.location manualmente
Componente padre
Props: siempre el camino más sencillo
Estado global para la comunicación padre-hijo
Entrada del usuario
Estado controlado (useState + onChange)
Refs no controladas para valores que necesitas validar o mostrar
Decisión clave: Prioriza los Server Components para la obtención de datos. Obtén datos en el cliente solo cuando necesites actualizaciones en tiempo real, refrescos iniciados por el usuario o scroll infinito.
Determina cómo fluyen los datos y los eventos entre este componente y el resto de la aplicación.
Patrón de comunicación
Cuándo usarlo
Ejemplo
Props hacia abajo
El padre pasa datos al hijo
<UserCard name={user.name} />
Callbacks hacia arriba
El hijo notifica un evento al padre
<SearchInput onSearch={handleSearch} />
Context
Muchos descendientes necesitan los mismos datos (tema, autenticación, locale)
useContext(ThemeContext)
Store global (Zustand)
Estado transversal compartido por componentes no relacionados
Cantidad del carrito en el header + página de checkout
Estado en la URL
Estado que debe sobrevivir a un refresco o poder compartirse
Filtros, paginación, pestaña activa
Server Actions
Envíos de formularios o mutaciones que llegan al servidor
<form action={submitForm}>
Decisión clave: Empieza con props y callbacks. Recurre a Context cuando estés pasando props a través de más de 3 niveles. Recurre a Zustand cuando varios componentes no relacionados necesiten el mismo estado.
Decisión clave: Tipa siempre las props como una interface (no en línea) cuando el componente tenga más de 2 props. Exporta la interfaz para que los consumidores puedan extenderla o referenciarla.
Declaraciones de función para componentes (hoisted, genéricos limpios). Ajústate a las reglas de lint de tu equipo.
Decisión clave: Sé coherente dentro de un archivo. Si el componente principal es una declaración de función, los helpers también deberían serlo. Mezclar estilos señala una diferencia intencional donde no la hay.
Optimiza solo cuando midas un problema. La optimización prematura añade complejidad sin ningún beneficio.
Síntoma
Herramienta
Cuándo aplicarla
El hijo se vuelve a renderizar cuando cambia el estado del padre
React.memo()
Solo si el hijo es costoso de renderizar y las props no han cambiado
Cálculo costoso en cada render
useMemo
Solo si el cálculo tarda más de 1 ms y las entradas rara vez cambian
Un callback provoca el re-render del hijo
useCallback
Solo al pasar callbacks a hijos memoizados
Renderizado de listas grandes
Virtualización (react-window, tanstack-virtual)
Listas con más de 100 elementos visibles en el DOM
Componente pesado en la carga inicial
React.lazy() + Suspense, o next/dynamic
Modales, gráficos, editores: cualquier cosa no visible en el primer pintado
Desplazamiento de layout por imágenes
next/image con width/height o fill
Toda imagen debe declarar sus dimensiones
Decisión clave: Perfila primero con el Profiler de React DevTools o la pestaña Performance de Chrome. Nunca añadas memo, useMemo o useCallback sin evidencia de un problema de rendimiento.
Relacionado:Patrones de rendimiento -- memoización, virtualización, división de código | useMemo -- cuándo ayuda la memoización (y cuándo perjudica) | useCallback -- referencias estables para props de hijos | React Compiler -- memoización automática en React 19 | Suspense -- carga perezosa y streaming
react-hook-form + esquema zod + componentes de formulario de shadcn/ui
Asistente de varios pasos
react-hook-form + useReducer para el estado de los pasos + esquemas zod por paso
Subida de archivos
<input type="file"> + FormData + server action o URL prefirmada
Validación en tiempo real (a medida que se escribe)
react-hook-form en modo "onChange" + zod
Actualizaciones optimistas
Hook useOptimistic para dar respuesta instantánea mientras el servidor procesa
Decisión clave: Para formularios simples, evita las librerías por completo: useActionState y useFormStatus, integrados en React 19, gestionan el estado pendiente, los errores y la mejora progresiva sin ninguna dependencia. Recurre a react-hook-form + zod cuando necesites validación compleja o muchos campos.
Elige tu estrategia de pruebas según la complejidad y el riesgo del componente.
Tipo de componente
Enfoque de pruebas
Pura visualización (sin estado)
Prueba de snapshot u omitirla: bajo riesgo, alta rotación
Interactivo (clics, entradas)
Prueba de integración con React Testing Library: simula acciones del usuario
Formulario con validación
Prueba el camino feliz y cada regla de validación: usa escenarios de Gherkin como especificaciones
Obtención de datos
Simula la API, prueba los estados de carga/error/éxito
Hook personalizado
Pruébalo con renderHook de React Testing Library
Flujo de trabajo complejo (varios pasos)
Prueba end-to-end con Playwright o Cypress
Decisión clave: Prueba el comportamiento, no la implementación. Si haces aserciones sobre el estado interno o una estructura del DOM específica, la prueba se romperá en cada refactorización. Haz aserciones sobre lo que el usuario ve y hace.
La accesibilidad no es opcional: es un requisito básico. Planifícala en el componente desde el principio.
Elemento
Requisitos mínimos
Botones
Usa <button>, no <div onClick>. Añade aria-label si es solo un icono.
Inputs
Asócialos con una <label> mediante htmlFor. Añade aria-describedby para los mensajes de error.
Modales / diálogos
Atrapa el foco, devuélvelo al cerrar. Usa role="dialog" y aria-modal="true".
Imágenes
Texto alt siempre. Las imágenes decorativas llevan alt="".
Navegación
Usa landmarks <nav>, <main>, <aside>. Asegúrate de que la navegación con teclado funcione.
Contenido dinámico
Usa regiones aria-live para contenido que se actualiza sin recargar la página.
Contraste de color
Mínimo 4.5:1 para texto normal, 3:1 para texto grande (WCAG AA).
Decisión clave: Usa primero elementos HTML semánticos (<button>, <nav>, <dialog>). Recurre a atributos ARIA solo cuando la semántica nativa no cubra tu caso de uso.
Organiza por funcionalidad, no por tipo. Coloca juntos los archivos relacionados.
Tipo de archivo
Ubicación
Ejemplo
Componente de página
app/[route]/page.tsx
app/dashboard/page.tsx
Layout
app/[route]/layout.tsx
app/dashboard/layout.tsx
Componente de UI compartido
components/[name].tsx
components/user-card.tsx
Componente específico de una funcionalidad
app/[feature]/components/[name].tsx
app/dashboard/components/stats-chart.tsx
Hook personalizado
hooks/[name].ts o junto a su componente
hooks/use-toggle.ts
Tipos
Colócalos en el mismo archivo, o types/[domain].ts para tipos compartidos
types/user.ts
Server Actions
app/[feature]/actions.ts
app/dashboard/actions.ts
Decisión clave: Si un componente lo usa una sola página, colócalo junto a esa página. Si lo usan 2 o más páginas, promuévelo a components/. No crees carpetas para un solo archivo.
Relacionado:App Router de Next.js -- convenciones de archivos, páginas y segmentos de ruta | Layouts de Next.js -- layouts anidados, templates y grupos de rutas
Inicio
|
v
1. ¿Qué renderiza? -----------> ¿Solo estático? --> Server Component (listo)
|
v
2. ¿Necesita interactividad? -> ¿No? --> Server Component con props (listo)
|
v (sí: añade "use client")
3. ¿Qué estado posee? -------> ¿Ninguno? --> Client Component sin estado
| ¿Simple? --> useState
| ¿Complejo? --> useReducer o hook personalizado
v
4. ¿De dónde vienen los datos? --> ¿Servidor? --> Obtenerlos en el Server Component padre y pasarlos como props
| ¿Cliente? --> SWR o TanStack Query
v
5. ¿Cómo se comunica? --------> ¿Padre-hijo? --> Props + callbacks
| ¿Transversal? --> Context o Zustand
v
6. Define los tipos de TypeScript
v
7. Elige el estilo de exportación
v
8. Optimiza solo si lo has medido
v
9. ¿Es un formulario? --------> ¿Simple? --> useActionState
| ¿Complejo? --> react-hook-form + zod
v
10. Planifica la estrategia de pruebas
v
11. Añade accesibilidad
v
12. Ubícalo en la estructura de archivos
v
Listo -- empieza a programar