React Hooks son funciones que permiten usar estado, efectos, contexto y otras características de React en componentes funcionales, sin escribir clases. Introducidos en React 16.8, los hooks cambiaron radicalmente el enfoque de desarrollo, reemplazando patrones complejos como HOC y render props. Según React, 2024, existen 15 hooks incorporados que cubren todos los escenarios de gestión de componentes.
Puntos clave
React Hooks son un conjunto de funciones integradas en React que permiten “conectarse” a los mecanismos internos del framework sin crear clases. Antes de React 16.8, los componentes funcionales solo podían aceptar props y devolver JSX — cualquier lógica compleja requería un componente de clase.
El problema de los componentes de clase es la división de la lógica relacionada. El código relacionado con una misma entidad (por ejemplo, una suscripción a WebSocket) se dividía forzosamente entre componentDidMount, componentDidUpdate y componentWillUnmount. Los hooks resuelven esto: toda la lógica se agrupa en un solo useEffect.
El segundo problema es la reutilización de la lógica. Para reutilizar la lógica de estado entre componentes de clase, había que aplicar HOC (Componentes de Orden Superior) o render props — ambos patrones creaban anidamiento adicional en el árbol de componentes. Los hooks personalizados resuelven esto sin anidamiento.
useState es el hook más básico. Recibe un valor inicial y devuelve una tupla: el valor actual y una función para actualizarlo. A diferencia de this.setState en las clases, useState no fusiona objetos — el nuevo estado reemplaza completamente al anterior para una llamada específica de useState.
import React, { useState } from 'react';
function Form() {
const [text, setText] = useState('');
const [submitting, setSubmitting] = useState(false);
async function handleSubmit() {
setSubmitting(true);
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify({ text })
});
setSubmitting(false);
}
return (
<div>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={handleSubmit}
disabled={submitting}>
{submitting ? 'Saving...' : 'Submit'}
</button>
</div>
);
}
El setter de useState puede aceptar una función que recibe el valor anterior y devuelve uno nuevo. La forma funcional es obligatoria cuando el nuevo estado se calcula a partir del anterior y es posible que haya llamadas setState agrupadas — cada llamada recibirá el valor anterior actual.
useEffect es un hook para realizar efectos secundarios en componentes funcionales. Recibe dos argumentos: una función de efecto y un array de dependencias. React ejecuta el efecto después de cada renderizado, pero cuando se especifican dependencias — solo cuando alguna de ellas cambia.
Un array de dependencias vacío [] significa que el efecto se ejecuta una vez después del primer renderizado (análogo a componentDidMount). Devolver una función desde el efecto es una limpieza que se ejecuta al desmontar (análogo a componentWillUnmount) y antes de volver a ejecutar el efecto.
import React, { useState, useEffect } from 'react';
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket(`wss://chat/${roomId}`);
socket.onmessage = (event) => {
setMessages(prev => [...prev, JSON.parse(event.data)]);
};
// Cierre de limpieza - cerrar socket
return () => socket.close();
}, [roomId]);
return <ul>
{messages.map((msg, i) =>
<li key={i}>{msg.text}</li>
)}
</ul>;
}
Las dependencias de useEffect son la única conexión entre el efecto y el mundo exterior. Si el efecto usa una variable de props o estado, debe estar en el array de dependencias. Ignorar esta regla provoca errores con cierres obsoletos (stale closures).
useContext es un hook para leer valores del contexto. Recibe un objeto de contexto creado por React.createContext y devuelve el valor actual. Si el contexto cambia, el componente se vuelve a renderizar automáticamente con el nuevo valor.
Antes de useContext, acceder al contexto en componentes funcionales requería envolver en Context.Consumer, lo que creaba anidamiento. useContext elimina completamente Consumer — basta con llamar al hook en cualquier lugar del componente funcional.
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext('claro');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button
className={`btn btn-${theme}`}
>
{theme === 'dark' ? 'moon' : '☀️'}
</button>;
}
useContext no es un reemplazo de Redux ni del estado global. React recomienda usar el contexto para valores que cambian con poca frecuencia — tema, configuración regional, estado de autenticación. Para datos que se actualizan con frecuencia, cada re-renderizado recalculará todos los consumidores del contexto, lo que puede reducir el rendimiento.
useReducer es una alternativa a useState para estados con lógica de actualización compleja. Recibe una función reductora y un estado inicial, devuelve el estado actual y una función dispatch. El reductor recibe el estado actual y una acción, devuelve un nuevo estado — una función pura sin efectos secundarios.
useReducer es especialmente útil cuando múltiples acciones (set, reset, increment, addItem) gestionan un mismo estado. En lugar de múltiples useState con diferentes setters — un solo reductor con tipos de acción claros. Esto hace que la lógica sea predecible y fácil de probar.
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'setStep':
return { ...state, step: action.step };
case 'reset':
return { count: 0, step: 1 };
default:
throw new Error('Unknown action: ' + action.type);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, {
count: 0,
step: 1
});
return (
<>
<p>Count: {state.count}</p>
<button
onClick={() => dispatch({ type: 'increment' })}>+</button>
<button
onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</>
);
}
useRef crea un objeto mutable que persiste entre renderizados. Cambiar current no provoca un re-renderizado. Los usos principales son acceder a elementos del DOM y almacenar valores mutables (temporizadores, valores anteriores, instancias de librerías).
useMemo memoiza el resultado de una función. Recalcula el valor solo cuando cambia una dependencia. Úselo para cálculos costosos — filtrado de arrays grandes, ordenación, formato de datos. useCallback es un caso especial de useMemo para memoizar funciones.
import React, { useRef, useMemo, useCallback } from 'react';
function SearchPage({ items, query, onItemSelect }) {
const inputRef = useRef(null);
// Autoenfoque al montar
useEffect(() => {
inputRef.current?.focus();
}, []);
// Memoizar resultado filtrado
const filtered = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase()
.includes(query.toLowerCase())
);
}, [items, query]);
// Memoizar callback
const handleSelect = useCallback((id) => {
onItemSelect(id);
}, [onItemSelect]);
return (
<div>
<input ref={inputRef}
value={query}
onChange={e => setQuery(e.target.value)}
/>
<ul>
{filtered.map(item =>
<li key={item.id}>
<button
onClick={() => handleSelect(item.id)}>
{item.name}
</button>
</li>
)}
</ul>
</div>
);
}
Una regla importante: useRef, useMemo y useCallback son herramientas de optimización, no construcciones obligatorias. No envuelva todo en useMemo — mida el rendimiento primero. La optimización prematura complica el código. React renderiza componentes rápidamente; useMemo solo es necesario cuando los cálculos son realmente costosos (O(n²) o superior).
React introdujo dos reglas estrictas para los hooks. Primera: llame a los hooks solo en el nivel superior — no dentro de condiciones, bucles o funciones anidadas. Segunda: llame a los hooks solo desde componentes funcionales de React o hooks personalizados. El plugin de ESLint eslint-plugin-react-hooks verifica automáticamente ambas reglas.
El error más común es violar el orden de llamada de los hooks. React depende del orden de llamada de los hooks entre renderizados. Si un hook está dentro de una condición y no se ejecuta en un renderizado, todos los hooks posteriores se desplazan, causando errores.
| Error | Qué sucede | Solución |
|---|---|---|
| Hook en una condición | El orden de los hooks se rompe | Traslade la lógica de la condición dentro del hook |
| Stale closure | El efecto usa un valor antiguo | Añada todas las dependencias al array |
| Bucle infinito | useEffect sin dependencias cambia el estado | Especifique dependencias o use useReducer |
| Mutación de ref en render | Efecto secundario en el cuerpo de la función | Mueva las mutaciones a useEffect |
El segundo error común es stale closure en useEffect. Si el efecto usa una variable no especificada en las dependencias, el efecto “captura” su valor en el momento de la creación del cierre. La solución es incluir siempre todas las variables usadas en el array de dependencias. Si hay demasiadas dependencias, considere dividir el efecto en varios.
Preguntas frecuentes
React depende del orden de llamada de los hooks entre renderizados. Si un hook se llama dentro de una condición y no se ejecuta en un renderizado, todos los hooks posteriores se desplazan — su estado se mezcla. La regla de ESLint hooks/exhaustive-deps advierte sobre esto.
useLayoutEffect es síncrono y se ejecuta antes de que el navegador pinte los cambios. useEffect es asíncrono — después del pintado. Use useLayoutEffect solo cuando necesite medir o cambiar el DOM antes de que el usuario vea los cambios (posición de popover, restauración de scroll).
Use AbortController dentro de useEffect. Cree un controller, pase la señal a fetch y llame a controller.abort() en la función de limpieza. Para navegadores antiguos, use un flag cancelled: return () => { cancelled = true; } con una verificación antes de setState.
Un hook personalizado es una función de JavaScript con el prefijo use que utiliza hooks incorporados. Créelo cuando la misma lógica con hooks se repite en dos o más componentes. Ejemplos: useDebounce, useLocalStorage, useMediaQuery. Los hooks personalizados reemplazan a HOC y render props.
En Strict Mode, React monta y desmonta intencionadamente el componente dos veces en desarrollo para encontrar errores con efectos no limpiados. Si la limpieza está escrita correctamente, la doble llamada no causa problemas. Esto no ocurre en modo de producción.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también