React desde cero: fundamentos que necesitas dominar
Aprende los fundamentos de React que todo desarrollador profesional debe dominar: componentes, hooks, estado, efectos y patrones de diseño reales.
React lleva más de una década dominando el desarrollo frontend y su hegemonía no da señales de menguar. Creado por Facebook en 2013 y liberado como open source, el paradigma basado en componentes que React popularizó ha influenciado a prácticamente todos los frameworks y librerías que han venido después. Hoy, entender React no es solo una ventaja competitiva para un desarrollador: es un prerequisito para acceder a la mayoría de posiciones frontend en el mercado laboral.
Sin embargo, React tiene una curva de aprendizaje que no siempre se explica bien. Muchos tutoriales enseñan la sintaxis JSX y los hooks más básicos pero no explican el modelo mental subyacente: cómo React decide cuándo re-renderizar un componente, por qué el estado es asíncrono, qué significa realmente que los componentes sean funciones puras o cuándo usar qué hook. Sin ese modelo mental, es fácil escribir código que funciona en casos sencillos pero falla de formas misteriosas cuando la complejidad crece.
Esta guía está pensada para quienes empiezan desde cero o quieren consolidar su comprensión de los fundamentos antes de avanzar a temas como Next.js, testing avanzado o arquitecturas de estado complejas. Cubriremos todos los conceptos esenciales con ejemplos reales y explicaciones que van más allá de la sintaxis para adentrarse en el porqué de cada decisión de diseño.
Qué es React y qué problema resuelve
Antes de React, el desarrollo de interfaces de usuario web complejas se gestionaba principalmente con jQuery o frameworks MVC como Backbone o Angular 1. El patrón típico era manipular el DOM directamente: buscar elementos, modificar su contenido, añadir clases, gestionar eventos. Este enfoque escala mal: cuando la interfaz tiene muchos estados posibles y varios elementos que deben mantenerse sincronizados entre sí, la lógica de actualización del DOM se vuelve una fuente inagotable de bugs.
React propuso una solución radicalmente diferente: en lugar de decirle al navegador qué cambiar en el DOM, describes cómo debería verse la interfaz para cada estado posible y dejas que React calcule automáticamente qué cambios del DOM son necesarios. Este cambio de imperativo a declarativo simplifica enormemente el razonamiento: en lugar de pensar en secuencias de mutaciones, piensas en funciones puras que mapean estado a interfaz. El Virtual DOM que React mantiene en memoria permite hacer esta reconciliación de forma eficiente.
JSX: JavaScript con superpoderes de markup
JSX es la extensión sintáctica que permite escribir marcado similar a HTML dentro de ficheros JavaScript. No es obligatorio usar JSX para trabajar con React —puedes usar React.createElement directamente— pero JSX hace el código enormemente más legible y es el estándar universal en todos los proyectos React reales. Un transpilador como Babel o el compilador de TypeScript convierte JSX a llamadas React.createElement antes de que el código llegue al navegador.
Hay algunas diferencias importantes entre JSX y HTML que debes interiorizar rápidamente. Los atributos usan camelCase en JSX: className en lugar de class, onClick en lugar de onclick, htmlFor en lugar de for. Las expresiones JavaScript se envuelven en llaves: {variable}, {condicion && elemento}, {lista.map(item => elemento)}. Todo JSX debe tener un único elemento raíz —o usar un fragmento (<>...</>)— y las etiquetas sin contenido deben cerrarse: <input />, <br />. Con estas reglas claras, JSX se convierte en una herramienta muy expresiva.
Componentes: la unidad fundamental de React
Un componente React es simplemente una función JavaScript que recibe un objeto de props como argumento y devuelve JSX. Esta simplicidad es engañosa: detrás hay un modelo muy potente de composición. Los componentes pueden recibir otros componentes como props (el patrón children), pueden recibir funciones como callbacks, pueden ser envueltos por otros componentes (Higher Order Components) o pueden compartir lógica a través de hooks personalizados. La composabilidad es la superpotencia de React.
Una buena práctica fundamental es mantener los componentes pequeños y con una única responsabilidad. Un componente de 500 líneas que hace fetch de datos, los filtra, los ordena, los muestra en una tabla con paginación y gestiona la selección múltiple es difícil de entender, testear y mantener. Mejor dividirlo en cinco componentes más pequeños, cada uno con su responsabilidad clara: un hook para el fetching, un componente para la tabla, otro para la paginación, otro para los controles de filtrado.
Props: la comunicación entre componentes
Las props son el mecanismo de comunicación hacia abajo en el árbol de componentes: el padre pasa datos al hijo a través de props, y el hijo no puede modificar esas props directamente. Este flujo unidireccional es una de las ideas más importantes de React: hace que el estado de la aplicación sea predecible y fácil de razonar. Cuando algo falla, sabes que el problema está en cómo el padre está calculando y pasando las props, no en mutaciones escondidas en componentes hijos.
Para comunicación hacia arriba —el hijo necesita notificar algo al padre— React usa callbacks: el padre pasa una función como prop y el hijo la llama cuando ocurre algo relevante. Este patrón, consistente y explícito, es la base sobre la que se construyen todos los flujos de interacción en React. Cuando la comunicación entre componentes no relacionados se vuelve tedious (prop drilling), es señal de que necesitas elevar el estado a un ancestro común o usar Context.
Estado con useState: datos que cambian con el tiempo
El hook useState es el mecanismo fundamental para gestionar datos que cambian con el tiempo dentro de un componente. Devuelve un par de valores: el estado actual y una función para actualizarlo. Cuando llamas a la función de actualización, React programa una re-renderización del componente con el nuevo valor de estado. Esta re-renderización es síncrona desde la perspectiva del desarrollador pero React puede agrupar múltiples actualizaciones de estado en una sola re-renderización para optimizar el rendimiento.
Un error muy común para desarrolladores nuevos en React es intentar leer el nuevo valor del estado inmediatamente después de llamar a setState. Como la actualización es asíncrona, el valor leído seguirá siendo el anterior. Si necesitas ejecutar lógica basada en el nuevo valor del estado, hazlo en el cuerpo del componente durante la siguiente renderización, o usa la forma funcional de setState que recibe el estado anterior como argumento: setState(prev => prev + 1).
Estado de objetos y arrays: inmutabilidad es clave
Cuando el estado es un objeto o un array, debes actualizar siempre creando nuevas referencias en lugar de mutar las existentes. React usa comparación por referencia para detectar cambios: si mutes el objeto directamente y pasas la misma referencia, React no detectará el cambio y no re-renderizará. El patrón correcto para objetos es usar el spread operator: setState({...estado, campo: nuevoValor}). Para arrays, usa métodos no mutantes como map, filter, concat o el spread operator en lugar de push, splice o sort directo.
useEffect: sincronizando con el mundo exterior
useEffect es el hook para gestionar efectos secundarios: operaciones que salen del flujo puro de renderizado, como llamadas a APIs, suscripciones a eventos, manipulación directa del DOM o timers. La firma básica es useEffect(callback, dependencias). El callback se ejecuta después de cada renderización en que alguna de las dependencias ha cambiado. Si el array de dependencias está vacío, el efecto se ejecuta solo una vez después del primer renderizado. Si se omite completamente, se ejecuta después de cada renderizado.
El array de dependencias es una fuente inagotable de bugs para desarrolladores que no entienden su semántica correctamente. Debes incluir en el array todas las variables reactivas (estado, props, valores calculados de estado o props) que uses dentro del callback del efecto. Omitir dependencias puede provocar efectos que leen valores obsoletos (stale closures). El plugin de ESLint react-hooks/exhaustive-deps detecta estos problemas automáticamente y es imprescindible en cualquier proyecto React serio.
Función de limpieza en useEffect
Cuando tu efecto crea una suscripción, un timer o cualquier recurso que necesita liberarse, debes devolver una función de limpieza desde el callback del efecto. React llamará a esta función de limpieza antes de ejecutar el efecto de nuevo (cuando las dependencias cambian) y cuando el componente se desmonta. Sin limpieza adecuada, acumulas suscripciones activas a eventos, timers que continúan ejecutándose después de que el componente ha desaparecido y potenciales memory leaks. Este es uno de los errores más frecuentes en aplicaciones React de mediana complejidad.
useContext: compartir datos sin prop drilling
React Context permite compartir datos entre componentes sin tener que pasarlos explícitamente por cada nivel del árbol como props. Es adecuado para datos que son genuinamente globales para un subárbol de componentes: el tema visual de la aplicación, el usuario autenticado, el idioma activo o configuraciones similares. Para usarlo, defines un contexto con createContext, envuelves los componentes que necesitan acceso en un Provider y lees el valor con useContext en cualquier componente hijo.
Context no es un reemplazo para librerías de gestión de estado como Zustand o Redux. Su limitación principal es el rendimiento: cuando el valor del contexto cambia, todos los componentes que usan ese contexto se re-renderizan, incluso si el dato específico que consumen no ha cambiado. Para estado que cambia con mucha frecuencia o estructuras de datos complejas con muchos consumidores, una librería de gestión de estado dedicada es más apropiada. Context brilla para datos que cambian poco y que necesitan estar disponibles en muchos puntos del árbol.
useReducer: gestión de estado complejo
useReducer es una alternativa a useState para gestionar estado complejo con múltiples sub-valores o cuando el siguiente estado depende del anterior de formas intrincadas. Sigue el patrón Redux: defines un reducer (función pura que recibe estado y acción y devuelve nuevo estado), inicializas el estado y despachas acciones para provocar transiciones de estado. La ventaja sobre múltiples useStates es que toda la lógica de transición está centralizada en el reducer, lo que facilita enormemente el testing y el razonamiento.
Un buen caso de uso para useReducer es un formulario multi-step con validación: cada campo, el paso actual, los errores de validación y el estado de envío son partes del mismo estado que interactúan entre sí. Modelarlo con un reducer hace explícitas todas las posibles transiciones (NEXT_STEP, PREV_STEP, SET_FIELD, VALIDATE, SUBMIT_START, SUBMIT_SUCCESS, SUBMIT_ERROR) y garantiza que el estado nunca se encuentre en una combinación inválida, como estar en el último paso con errores de validación sin resolver.
useMemo y useCallback: optimización del rendimiento
useMemo memoriza el resultado de un cálculo costoso y solo lo recalcula cuando sus dependencias cambian. useCallback memoriza una referencia a una función para que sea estable entre renderizaciones. Ambos son herramientas de optimización y el error más común es usarlos en exceso, añadiendo complejidad sin beneficio real. React es muy rápido en re-renderizados simples; la mayoría de aplicaciones no necesitan estas optimizaciones hasta que los perfiles de rendimiento indiquen un cuello de botella real.
Los casos donde useMemo es realmente útil son cálculos verdaderamente costosos (filtrar y ordenar listas de miles de elementos en cada renderizado) y estabilizar referencias de objetos que se pasan como dependencias a useEffect o como props a componentes memorizados con React.memo. useCallback es especialmente valioso cuando pasas callbacks a componentes hijos que están envueltos en React.memo, porque una nueva referencia de función provocaría una re-renderización innecesaria del hijo aunque la lógica de la función no haya cambiado.
Hooks personalizados: reutilización de lógica con estado
Los hooks personalizados son funciones JavaScript que empiezan por use y pueden llamar a otros hooks. Son la forma canónica de reutilizar lógica con estado entre componentes. Un hook personalizado puede encapsular la lógica de fetching de datos, la gestión de formularios, el seguimiento del tamaño de la ventana, la persistencia en localStorage o cualquier otro comportamiento que quieras compartir entre varios componentes sin repetir código.
Un hook bien diseñado tiene una interfaz clara: recibe los parámetros mínimos necesarios y devuelve los valores y funciones que el componente consumidor necesita. Por ejemplo, un hook useFetch podría recibir una URL y opciones, y devolver { data, loading, error, refetch }. El componente que lo usa no necesita saber nada sobre cómo se gestiona el estado interno, las condiciones de carrera o la cancelación de peticiones: todo eso está encapsulado en el hook.
Manejo de errores en React
React proporciona el concepto de Error Boundaries: componentes de clase (actualmente no disponibles como funciones, aunque hay propuestas en marcha) que capturan errores de JavaScript en su subárbol de componentes y muestran una UI de fallback en lugar de colapsar toda la aplicación. La librería react-error-boundary simplifica enormemente su uso y proporciona una API ergonómica basada en el componente ErrorBoundary con prop fallback y el hook useErrorBoundary.
Una buena práctica es envolver secciones independientes de la aplicación en Error Boundaries separados para que el fallo de una sección no derribe toda la interfaz. El panel de comentarios de un artículo, el widget de noticias relacionadas y el formulario de suscripción pueden tener sus propios Error Boundaries, de modo que si el widget de noticias falla por un error inesperado de la API, el resto del artículo sigue siendo legible y funcional.
Patrones de diseño fundamentales en React
- Compound Components: componentes que trabajan juntos compartiendo estado implícito, como Select + Option o Tabs + Tab + TabPanel. Ofrecen una API declarativa y flexible.
- Render Props: pasar una función como prop que el componente hijo llama para renderizar contenido, permitiendo compartir lógica mientras el padre controla el renderizado.
- Container/Presentational: separar la lógica de fetching y transformación de datos (container) de la presentación visual (presentational). Facilita el testing y la reutilización.
- Controlled vs Uncontrolled Components: en componentes controlados, React gestiona el valor del input mediante state. En no controlados, el DOM gestiona su propio estado y se accede con refs.
- Provider Pattern: usar Context + Provider para inyectar dependencias (como un cliente API o un theme) en profundidad del árbol sin prop drilling.
Herramientas de desarrollo imprescindibles
React DevTools es la extensión de navegador oficial para inspeccionar el árbol de componentes React, ver las props y el estado de cada componente, identificar re-renderizaciones innecesarias y perfilar el rendimiento. Es la primera herramienta que debes instalar cuando empiezas con React y una de las más valiosas en proyectos de cualquier tamaño. La pestaña Profiler en particular es esencial para identificar qué componentes se re-renderizan innecesariamente y cuánto tiempo tarda cada renderizado.
En el ecosistema de testing, React Testing Library se ha convertido en el estándar para testear componentes React. Su filosofía —testear comportamiento observable por el usuario, no detalles de implementación— produce tests más robustos que no se rompen con refactorizaciones internas. Eslint con los plugins react y react-hooks es imprescindible para detectar problemas comunes antes de que lleguen a producción. Prettier mantiene un formato de código consistente en todo el equipo y elimina las discusiones sobre estilo.
Errores comunes al aprender React
- Mutar el estado directamente: setState({ obj.field = value }) no provoca re-renderización. Siempre crea nuevas referencias.
- Olvidar las dependencias de useEffect: lleva a stale closures donde el efecto lee valores desactualizados.
- Hacer fetch en cada renderizado sin array de dependencias: provoca bucles infinitos de peticiones.
- Inicializar estado con props directamente: el estado se inicializa solo una vez; cambios posteriores en las props no actualizan el estado automáticamente. Usa efectos o props controladas.
- Colocar demasiada lógica en componentes: hacer componentes grandes y difíciles de mantener en lugar de extraer hooks y sub-componentes.
- Usar índices de array como keys en listas: cuando el orden cambia, React confunde los elementos y provoca bugs visuales sutiles.
El camino hacia la maestría en React
Dominar React es un proceso gradual que se consolida construyendo proyectos reales de complejidad creciente. El conocimiento teórico es necesario pero insuficiente: necesitas enfrentarte a los problemas reales —gestionar el estado de un formulario complejo, optimizar una lista larga, implementar un sistema de drag and drop, manejar errores de red de forma robusta— para interiorizar verdaderamente los conceptos.
Una vez sólidos los fundamentos cubiertos en este artículo, los siguientes pasos naturales son profundizar en la gestión de estado con Zustand o Jotai, aprender a testear componentes con React Testing Library, explorar Next.js para aplicaciones con SSR, y familiarizarse con React Query o TanStack Query para la sincronización de estado de servidor. La documentación oficial de React, completamente renovada en 2023, es el mejor recurso disponible y un referente que debes consultar regularmente.
React no es solo una librería de UI; es un modelo mental para construir interfaces declarativas, composables y mantenibles. Invertir en entender ese modelo mental es la mejor decisión que puedes tomar como desarrollador frontend.
Consultor TI. Especializado en sistemas, redes y ciberseguridad.
Más sobre nosotros →Comentarios
Sé el primero en comentar.
Deja tu comentario
Sigue leyendo
Next.js para aplicaciones web profesionales: guía completa
Descubre cómo Next.js se ha convertido en el framework de referencia para construir aplicaciones web profesionales escalables, con SSR, SSG y App Router.
Diseña una API REST robusta y segura
Aprende a diseñar APIs REST profesionales: convenciones de URL, versionado, autenticación, control de errores, documentación y buenas prácticas de seguridad.
Bases de datos para tu aplicación: SQL vs NoSQL
Compara SQL y NoSQL en profundidad: cuándo usar PostgreSQL, MySQL, MongoDB o Redis, con criterios prácticos para elegir la base de datos correcta.