Los React Hooks surgieron para resolver problemáticas fundamentales en el desarrollo de aplicaciones con componentes de clase. Permiten reutilizar la lógica de estado sin cambiar la jerarquía de los componentes, evitan la complejidad del manejo de this y facilitan la división de componentes masivos en funciones más pequeñas y legibles.
Existen dos reglas críticas para el uso de Hooks:
- Solo deben invocarse en el nivel superior de la función. No se pueden usar dentro de bucles, condiciones o funciones anidadas.
- Solo pueden llamarse desde componentes funcionales de React o desde otros Hooks personalizados.
useState
Este Hook permite integrar variables de estado en componentes funcionales. El estado en React debe tratarse como inmutable; cuando se actualiza, el componente se vuelve a renderizar con el nuevo valor.
Para manejar colecciones o estructuras complejas, es fundamental utilizar el operador spread para mantener la integridad de los datos no modificados:
import { useState } from "react";
function GestorTareas() {
const [lista, setLista] = useState([
{ id: 101, tarea: 'Analizar requerimientos' },
{ id: 102, tarea: 'Diseñar base de datos' }
]);
const eliminarTarea = (id) => {
// Filtramos para obtener un nuevo array, manteniendo la inmutabilidad
setLista(lista.filter(item => item.id !== id));
};
return (
<div>
<ul>
{lista.map(item => (
<li key={item.id}>
{item.tarea}
<button onClick={() => eliminarTarea(item.id)}>Borrar</button>
</li>
))}
</ul>
</div>
);
}
Debido a que las actualizaciones de useState son asíncronas, leer el valor inmediatamente después de un set puede devolver el valor antiguo. En escenarios donde se requiere el valor más reciente dentro de cierres (closures) o temporizadores, se recomienda combinarlo con useRef.
useReducer
Cuando la lógica del estado es compleja o el siguiente estado depende estrictamente del anterior, useReducer es preferible a useState. Centraliza las transiciones de estado mediante una función reductora pura.
import { useReducer } from "react";
const inicial = { cantidad: 0 };
function reductor(estado, accion) {
switch (accion.tipo) {
case 'incrementar':
return { cantidad: estado.cantidad + accion.monto };
case 'decrementar':
return { cantidad: estado.cantidad - 1 };
default:
return estado;
}
}
function ContadorAvanzado() {
const [estado, dispatch] = useReducer(reductor, inicial);
return (
<>
<h1>Total: {estado.cantidad}</h1>
<button onClick={() => dispatch({ tipo: 'incrementar', monto: 5 })}>+5</button>
<button onClick={() => dispatch({ tipo: 'decrementar' })}>-1</button>
</>
);
}
useRef
useRef devuelve un objeto mutable cuya propiedad .current persiste durante todo el ciclo de vida del componente. Se utiliza principalmente para:
- Acceder directamente a nodos del DOM.
- Almacenar valores que no deben disparar un nuevo renderizado al cambiar.
import { useRef } from "react";
function CampoEnfoque() {
const inputRef = useRef(null);
const activarFoco = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={activarFoco}>Enfocar Input</button>
</div>
);
}
Para exponer métodos de un componente hijo a un padre, se utiliza useImperativeHandle junto con forwardRef.
useContext
Permite el paso de datos a través del árbol de componentes sin necesidad de pasar props manualmente en cada nivel. Es ideal para temas visuales, configuraciones de idioma o estados de autenticación.
import { createContext, useContext } from "react";
const TemaContexto = createContext('claro');
function ComponenteNieto() {
const tema = useContext(TemaContexto);
return <div className={tema}>El tema actual es: {tema}</div>;
}
function App() {
return (
<TemaContexto.Provider value="oscuro">
<ComponenteNieto />
</TemaContexto.Provider>
);
}
useEffect
Este Hook gestiona efectos secundarios como peticiones API, suscripciones o manipulaciones manuales del DOM. Se ejecuta después del renderizado.
import { useState, useEffect } from "react";
function MonitorRastreo() {
const [posicion, setPosicion] = useState({ x: 0, y: 0 });
useEffect(() => {
const actualizarMouse = (e) => {
setPosicion({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', actualizarMouse);
// Función de limpieza para evitar fugas de memoria
return () => {
window.removeEventListener('mousemove', actualizarMouse);
};
}, []); // Array vacío indica que solo se ejecuta al montar y desmontar
return <p>Coordenadas: {posicion.x}, {posicion.y}</p>;
}
useMemo y useCallback
Ambos Hooks se utilizan para la optimización de rendimiento a través de la memorización:
- useMemo: Memoriza el resultado de una función costosa. Solo se recalcula si las dependencias cambian.
- useCallback: Memoriza la instancia de la función. Es útil cuando pasamos funciones a componentes hijos optiimzados con
React.memo.
import { useState, useMemo, useCallback } from "react";
function CalculoPesado({ listaNumeros }) {
const [filtro, setFiltro] = useState(0);
// Memoriza el valor calculado
const sumaTotal = useMemo(() => {
return listaNumeros.reduce((acc, curr) => acc + curr, 0);
}, [listaNumeros]);
// Memoriza la función para evitar renders innecesarios en hijos
const manejarEvento = useCallback(() => {
console.log("Acción ejecutada");
}, []);
return <div>Suma: {sumaTotal}</div>;
}
Hooks Personalizados
Un Hook personalizado es simplemente una función JavaScript que utiliza otros Hooks para encapsular lógica reutilizable. Por convención, siempre deben comenzar con la palabra use.
import { useState, useEffect } from "react";
function useConectividad() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const handleStatus = () => setOnline(navigator.onLine);
window.addEventListener('online', handleStatus);
window.addEventListener('offline', handleStatus);
return () => {
window.removeEventListener('online', handleStatus);
window.removeEventListener('offline', handleStatus);
};
}, []);
return online;
}
Además de los Hooks nativos, existen ecosistemas robustos como ahooks y react-use, así como Hooks integrados en librerías estándar como useNavigate de React Router.