Cómo Usar React Hooks: Una Guía Completa con Ejemplos
Cómo Usar React Hooks: Una Guía Completa con Ejemplos
Cuando React 16.8 introdujo los Hooks a principios de 2019, cambió fundamentalmente la forma en que los desarrolladores construyen aplicaciones React al permitir que los componentes funcionales gestionen estado, efectos secundarios y contexto sin necesidad de escribir componentes de clase. Comprender cómo usar React hooks de manera efectiva es ahora esencial para cualquier desarrollador moderno de React, ya que los Hooks se han convertido en "la única forma en que se escribe la mayor parte del código React". Esta guía te explicará los Hooks integrados esenciales, las reglas que los rigen y te proporcionará ejemplos prácticos que puedes usar de inmediato.
Lo Que Aprenderás
Al final de esta guía, usarás con confianza los Hooks principales de React —useState, useEffect, useContext, useRef, useReducer, useMemo y useCallback— en tus propios proyectos. Entenderás cuándo elegir un Hook sobre otro, cómo evitar errores comunes como renderizados innecesarios y cómo extraer lógica reutilizable en Hooks personalizados. La conclusión más importante es que los Hooks son solo funciones: aprender a componerlos es la clave para dominar React.
Comprendiendo las Reglas de los Hooks
Antes de sumergirte en Hooks individuales, debes entender dos reglas no negociables impuestas por el paquete eslint-plugin-react-hooks:
Solo llama a los Hooks en el nivel superior. Nunca llames a Hooks dentro de bucles, condiciones o funciones anidadas. React depende del orden en que se llaman los Hooks para preservar el estado entre renderizados; si el orden cambia, React no puede asociar correctamente el estado con su Hook.
Solo llama a los Hooks desde funciones de React. Solo puedes llamar a Hooks desde componentes funcionales o desde otros Hooks personalizados. Una función JavaScript normal no puede llamar a
useStateouseEffect.Google AdInline article slot
De la segunda regla surge una convención: cualquier función que llame a Hooks debe comenzar con use (por ejemplo, useCustomHook). El linter usa este prefijo para aplicar las reglas correctamente.
Los Hooks Más Esenciales Que Usarás a Diario
useState: Gestionando el Estado del Componente
El Hook useState es el más fundamental, ya que te permite agregar estado a componentes funcionales. Devuelve un array con dos elementos: el valor actual del estado y una función para actualizarlo.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // El estado inicial es 0
return (
<button onClick={() => setCount(count + 1)}>
Clickeado {count} veces
</button>
);
}
Cuando actualices el estado basándote en el valor anterior, usa la forma de actualización funcional para evitar condiciones de carrera, especialmente con el agrupamiento automático de React:
// Seguro: usa el valor de estado anterior
setCount(prevCount => prevCount + 1);
// También seguro para objetos: extiende el estado anterior
setUser(prevUser => ({ ...prevUser, name: 'Jane' }));
Si calcular el estado inicial es costoso, pasa una función en lugar de un valor; React la ejecutará solo en el renderizado inicial:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
useEffect: Manejando Efectos Secundarios
El Hook useEffect te permite realizar efectos secundarios en componentes funcionales, como obtener datos, configurar suscripciones o modificar manualmente el DOM. Por defecto, los efectos se ejecutan después de cada renderizado completo, pero puedes controlar cuándo se disparan usando el array de dependencias.
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// Este efecto se ejecuta después de que el componente se monta y cuando userId cambia
fetchUser(userId).then(data => setUser(data));
// Función de limpieza opcional que se ejecuta antes del siguiente efecto y al desmontar
return () => {
// Cancela cualquier solicitud pendiente o limpia suscripciones
};
}, [userId]); // Array de dependencias: se vuelve a ejecutar solo cuando userId cambia
return <div>{user?.name}</div>;
}
Patrones clave para useEffect:
- Array de dependencias vacío
[]: El efecto se ejecuta una vez después del renderizado inicial (montaje). - Sin array de dependencias: El efecto se ejecuta después de cada renderizado (rara vez es lo que quieres).
- Función de limpieza: Previene fugas de memoria limpiando suscripciones, temporizadores o solicitudes de red pendientes.
Advertencia importante: No todo cambio de estado requiere un efecto. Si puedes calcular un valor durante el renderizado, hazlo directamente en lugar de usar useEffect:
// ❌ Efecto innecesario
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
// ✅ Calcula directamente durante el renderizado
const fullName = `${firstName} ${lastName}`;
useContext: Consumiendo Contexto Sin Prop Drilling
El Hook useContext te permite leer valores del Contexto de React sin envolver tu componente en un componente Consumer:
import { useContext } from 'react';
// Crea un contexto
const ThemeContext = React.createContext('light');
function ThemedButton() {
// Lee el valor del contexto directamente
const theme = useContext(ThemeContext);
return <button className={theme}>Haz clic</button>;
}
Cuando el valor del proveedor de contexto cambia, cualquier componente que use useContext se vuelve a renderizar automáticamente.
useRef: Referenciando Valores Que No Provocan Renderizados
El Hook useRef crea un objeto ref mutable cuya propiedad .current persiste entre renderizados. A diferencia del estado, actualizar una ref no provoca un nuevo renderizado. Esto hace que las refs sean ideales para:
- Almacenar referencias a elementos del DOM
- Mantener un registro de IDs de temporizadores
- Persistir valores que no deberían desencadenar actualizaciones de la interfaz de usuario
import { useRef, useEffect } from 'react';
function TextInput() {
const inputRef = useRef(null);
useEffect(() => {
// Enfoca el input después de que el componente se monta
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
useReducer: Gestionando Lógica de Estado Compleja
Para estados que involucran múltiples subvalores o transiciones complejas, useReducer suele ser preferible a useState. Acepta una función reductora y un estado inicial, y devuelve el estado actual y una función dispatch:
import { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: action.payload };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset', payload: 0 })}>Reset</button>
</>
);
}
Hooks de Optimización de Rendimiento
useMemo: Almacenando en Caché Cálculos Costosos
useMemo almacena en caché el resultado de un cálculo y solo lo recalcula cuando las dependencias cambian:
import { useMemo } from 'react';
function TodoList({ todos, filter }) {
// Solo recalcula cuando todos o filter cambian
const filteredTodos = useMemo(() => {
return todos.filter(todo => todo.status === filter);
}, [todos, filter]);
return (
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
useCallback: Memorizando Referencias de Funciones
useCallback devuelve una versión memorizada de una función callback que solo cambia si las dependencias cambian. Esto es particularmente útil al pasar callbacks a componentes hijos optimizados que dependen de la igualdad de referencia para evitar renderizados innecesarios:
import { useCallback } from 'react';
function ParentComponent() {
const handleClick = useCallback(() => {
console.log('Botón clickeado');
}, []); // La referencia de la función se mantiene estable entre renderizados
return <ChildButton onClick={handleClick} />;
}
Hooks Personalizados: Reutilizando Lógica Entre Componentes
El verdadero poder de los Hooks surge cuando creas Hooks personalizados para extraer y reutilizar lógica con estado. Un Hook personalizado es simplemente una función JavaScript cuyo nombre comienza con use y que puede llamar a otros Hooks.
Ejemplo: useLocalStorage
Este Hook personalizado sincroniza el estado con localStorage, persistiendo datos entre recargas de página:
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
if (typeof window === 'undefined') return initialValue;
const stored = localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Uso
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Cambiar a modo {theme === 'light' ? 'oscuro' : 'claro'}
</button>
);
}
Ejemplo: useMediaQuery
Este Hook te permite renderizar componentes condicionalmente basándote en consultas de medios CSS, como la preferencia del usuario por movimiento reducido:
import { useState, useEffect } from 'react';
function useMediaQuery(query) {
const [matches, setMatches] = useState(() => {
if (typeof window === 'undefined') return false;
return window.matchMedia(query).matches;
});
useEffect(() => {
const mediaQueryList = window.matchMedia(query);
const onChange = (event) => setMatches(event.matches);
mediaQueryList.addEventListener('change', onChange);
return () => mediaQueryList.removeEventListener('change', onChange);
}, [query]);
return matches;
}
// Uso
function MotionAwareComponent() {
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
return prefersReducedMotion ? <StaticHero /> : <AnimatedHero />;
}
React 19: Nuevos Hooks para Formularios y Datos Asíncronos
React 19 (lanzado en diciembre de 2024) introdujo varios Hooks nuevos diseñados para simplificar el manejo de formularios y operaciones asíncronas.
useActionState: Simplificando el Envío de Formularios
useActionState gestiona el estado de envío de formularios, devolviendo el último resultado, una función de acción y un indicador de pendiente:
import { useActionState } from 'react';
async function subscribeAction(prevState, formData) {
const email = formData.get('email');
try {
await subscribe(email);
return { ok: true };
} catch (error) {
return { ok: false, error: error.message };
}
}
function NewsletterForm() {
const [state, formAction, isPending] = useActionState(
subscribeAction,
{ ok: false }
);
return (
<form action={formAction}>
<input name="email" type="email" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Suscribiendo...' : 'Suscribirse'}
</button>
{state.ok && <p>¡Estás suscrito!</p>}
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
</form>
);
}
useFormStatus: Accediendo al Estado del Formulario Padre
useFormStatus permite a los componentes anidados saber si su formulario padre se está enviando:
import { useFormStatus } from 'react';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Guardando...' : 'Guardar'}
</button>
);
}
Integración con Redux: El Hook useSelector
Cuando usas Redux con React, el Hook useSelector es la forma moderna y recomendada de acceder al estado del store de Redux. Reemplaza el antiguo componente de orden superior connect y funciona mejor con TypeScript.
import { useSelector, useDispatch } from 'react-redux';
function CounterComponent() {
// Extrae datos específicos del store de Redux
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>Incrementar</button>
</div>
);
}
Importante: useSelector usa igualdad de referencia estricta === por defecto. Devolver un nuevo objeto cada vez forzará renderizados. Usa selectores memorizados o llama a useSelector varias veces para campos individuales para optimizar el rendimiento:
// ❌ Esto provoca renderizados en cada acción
const { count, user } = useSelector((state) => ({
count: state.count,
user: state.user
}));
// ✅ Llama a useSelector para cada campo
const count = useSelector((state) => state.count);
const user = useSelector((state) => state.user);
Fuentes
- Documentación Oficial de React: Hooks Integrados de React
- Documentación Oficial de React: Referencia de la API de Hooks
- Documentación de React Redux: API de Hooks
- Patterns.dev: Patrón de Hooks
- Documentación Oficial de React: Usando el Hook de Estado
— Editorial Team
Aún no hay comentarios.