En esta página
useRef, useMemo y useCallback
useRef — acceso directo al DOM
useRef crea una referencia mutable que persiste entre renders. Su uso más común es obtener acceso directo a elementos del DOM:
import { useRef } from 'react';
function CampoConFoco(): React.JSX.Element {
const inputRef = useRef<HTMLInputElement>(null);
const enfocarInput = () => {
// current es el elemento HTML real
inputRef.current?.focus();
};
return (
<>
<input ref={inputRef} type="text" placeholder="Escribe aquí" />
<button type="button" onClick={enfocarInput}>
Enfocar
</button>
</>
);
}El tipo genérico <HTMLInputElement> le dice a TypeScript qué tipo de elemento esperar. El valor de inputRef.current es HTMLInputElement | null porque el ref no apunta a nada hasta que el componente se monta.
Casos de uso de useRef para elementos DOM
function ReproductorVideo(): React.JSX.Element {
const videoRef = useRef<HTMLVideoElement>(null);
const reproducir = () => {
void videoRef.current?.play();
};
const pausar = () => {
videoRef.current?.pause();
};
const irAlInicio = () => {
if (videoRef.current) {
videoRef.current.currentTime = 0;
}
};
return (
<div>
<video ref={videoRef} src="/video/tutorial.mp4" />
<button type="button" onClick={reproducir}>▶ Reproducir</button>
<button type="button" onClick={pausar}>⏸ Pausar</button>
<button type="button" onClick={irAlInicio}>⏮ Inicio</button>
</div>
);
}useRef para valores persistentes sin render
useRef también sirve para almacenar valores que persisten entre renders pero cuyo cambio no debe provocar un re-render:
function AutoguardadoEditor(): React.JSX.Element {
const [contenido, setContenido] = useState('');
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const ultimoGuardadoRef = useRef<string>('');
const manejarCambio = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const nuevoTexto = e.target.value;
setContenido(nuevoTexto);
// Limpiar timer anterior (debounce)
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
if (nuevoTexto !== ultimoGuardadoRef.current) {
ultimoGuardadoRef.current = nuevoTexto;
console.log('Guardando:', nuevoTexto);
// llamar a la API de guardado
}
}, 1000);
};
return (
<textarea value={contenido} onChange={manejarCambio} />
);
}useMemo — memoización de cálculos costosos
useMemo calcula un valor y lo memoiza: solo lo recalcula cuando sus dependencias cambian.
function AnaliticaVentas({ ventas }: { ventas: Venta[] }): React.JSX.Element {
const [filtroPeriodo, setFiltroPeriodo] = useState<'mes' | 'trimestre' | 'año'>('mes');
// Este cálculo es costoso: filtrado + suma + estadísticas
const estadisticas = useMemo(() => {
const filtradas = ventas.filter((v) => v.periodo === filtroPeriodo);
const total = filtradas.reduce((sum, v) => sum + v.monto, 0);
const promedio = filtradas.length > 0 ? total / filtradas.length : 0;
const maxima = Math.max(...filtradas.map((v) => v.monto));
return { total, promedio, maxima, cantidad: filtradas.length };
}, [ventas, filtroPeriodo]); // Solo recalcula si ventas o filtroPeriodo cambian
return (
<div>
<select
value={filtroPeriodo}
onChange={(e) => setFiltroPeriodo(e.target.value as 'mes' | 'trimestre' | 'año')}
>
<option value="mes">Este mes</option>
<option value="trimestre">Este trimestre</option>
<option value="año">Este año</option>
</select>
<p>Total: ${estadisticas.total.toLocaleString('es-ES')}</p>
<p>Promedio: ${estadisticas.promedio.toFixed(2)}</p>
<p>Máxima: ${estadisticas.maxima}</p>
</div>
);
}useCallback — referencias estables de funciones
useCallback memoiza una función: retorna la misma referencia mientras sus dependencias no cambien. Es útil cuando pasas callbacks a componentes que están envueltos en React.memo:
function Padre(): React.JSX.Element {
const [valor, setValor] = useState(0);
const [lista, setLista] = useState<string[]>([]);
// ❌ Sin useCallback: nueva función en cada render → Hijo siempre re-renderiza
const agregarItem = (item: string) => {
setLista((prev) => [...prev, item]);
};
// ✅ Con useCallback: misma referencia → Hijo solo re-renderiza si la función cambia
const agregarItemEstable = useCallback((item: string) => {
setLista((prev) => [...prev, item]);
}, []); // No depende de nada gracias al updater funcional
return (
<div>
<button type="button" onClick={() => setValor((v) => v + 1)}>
Valor: {valor}
</button>
<HijoMemoizado onAgregar={agregarItemEstable} />
</div>
);
}React.memo — evitar re-renders del componente
React.memo es un Higher Order Component que memoriza el resultado del render. Solo vuelve a renderizar si las props cambian:
interface FilaProps {
nombre: string;
precio: number;
onEditar: (nombre: string) => void;
}
// Con memo: solo re-renderiza si nombre, precio u onEditar cambian
const FilaProducto = memo(function FilaProducto({ nombre, precio, onEditar }: FilaProps) {
return (
<tr>
<td>{nombre}</td>
<td>${precio}</td>
<td>
<button type="button" onClick={() => onEditar(nombre)}>Editar</button>
</td>
</tr>
);
});
// Comparación personalizada si la comparación por referencia no es suficiente
const FilaProductoCustom = memo(
function FilaProducto({ nombre, precio, onEditar }: FilaProps) {
return <tr><td>{nombre}</td><td>${precio}</td></tr>;
},
(prevProps, nextProps) => {
// Retorna true si son "iguales" (no re-renderizar)
return prevProps.nombre === nextProps.nombre && prevProps.precio === nextProps.precio;
}
);El flujo de optimización correcto
El proceso correcto para optimizar renders es:
- Mide primero con React DevTools Profiler
- Identifica qué componentes se renderizan innecesariamente
- Aplica
React.memoal componente que tarda - Estabiliza los callbacks con
useCallbacksi son props del componente memoizado - Memoiza cálculos costosos con
useMemosi el Profiler los identifica
Con React Compiler en proyectos nuevos, muchos de estos pasos son automáticos. Pero entender los fundamentos te permite depurar cuando algo no funciona como esperas.
Inicia sesión para guardar tu progreso