¿Qué es useRef y en qué se diferencia de useState?
Ver respuesta — intenta responderla en voz alta primero
useRef te da una caja mutable ({ current: ... }) que sobrevive entre renders pero que NO provoca un re-render cuando cambia. useState también sobrevive, pero cada actualización sí re-renderiza el componente.
Piensa en useRef como una variable de instancia: un lugar donde guardar algo que necesitas recordar entre renders, pero que no afecta lo que se pinta en pantalla.
Los puntos clave:
- Devuelve siempre el mismo objeto
{ current: valorInicial }en todos los renders. Puedes leer y escribirref.currentcuando quieras. - Cambiar
ref.currentno re-renderiza. Por eso sirve para IDs de timers, contadores internos, banderas como "¿ya se montó?", o el valor anterior de una prop. - Referencias al DOM: si pasas la ref al atributo
refde un elemento JSX, React coloca el nodo real encurrentdespués de montar. Así puedes hacer focus, medir tamaños o hacer scroll. - Tipado: para el DOM se usa
useRef<HTMLInputElement>(null). Empieza ennullporque el nodo no existe hasta que React monta; por eso al usarla verificas con?.o un if. - Regla de oro: si el valor se muestra en la UI, es useState. Si solo lo necesitas recordar entre renders, es useRef.
- No leas ni escribas refs durante el render: hazlo en handlers o efectos, para que el render siga siendo puro.
import { useRef, useState } from "react";
function BuscadorConFoco() {
// Ref al DOM: empieza en null hasta que React monta el input
const inputRef = useRef<HTMLInputElement>(null);
// Ref como valor mutable: cuenta clics SIN provocar renders
const clicsSinRender = useRef(0);
const [busqueda, setBusqueda] = useState("");
function enfocarInput() {
clicsSinRender.current += 1; // no re-renderiza
console.log(`Clics acumulados: ${clicsSinRender.current}`);
inputRef.current?.focus(); // acceso directo al nodo real
}
return (
<div>
<input
ref={inputRef}
value={busqueda}
onChange={e => setBusqueda(e.target.value)} // esto SÍ re-renderiza
placeholder="Escribe tu búsqueda"
/>
<button onClick={enfocarInput}>Enfocar</button>
{/* Comportamiento esperado: al hacer clic, el input recibe el foco.
El contador de la ref sube en consola, pero la UI no se repinta
por ese cambio: solo se repinta cuando cambia "busqueda". */}
</div>
);
}
Usar una ref para algo que debe verse en pantalla:
// MAL: la UI nunca se actualiza porque cambiar la ref no re-renderiza
const contador = useRef(0);
return (
<button onClick={() => { contador.current += 1; }}>
Clics: {contador.current} {/* se queda congelado en pantalla */}
</button>
);
// BIEN: si se muestra en la UI, es estado
const [contador, setContador] = useState(0);
return (
<button onClick={() => setContador(prev => prev + 1)}>
Clics: {contador}
</button>
);
El error inverso también existe: usar useState para el ID de un setInterval genera renders inútiles cada vez que lo guardas. Ese ID nadie lo ve: va en una ref.
useRef me da un objeto mutable con una propiedad
currentque persiste entre renders, pero con una diferencia clave frente a useState: modificar la ref no provoca un re-render. Por eso la uso para valores que necesito recordar pero que no se pintan, como IDs de timers o el valor anterior de una prop. Su otro uso principal es acceder a elementos del DOM: la tipo comouseRef<HTMLInputElement>(null), la paso al atributorefdel elemento, y después del montaje puedo hacer focus o medirlo. Mi regla mental es: si el dato aparece en la UI, useState; si solo lo recuerdo entre renders, useRef.
Crea un componente ReproductorSimple con un <video> y dos botones: "Reproducir" y "Pausar", usando una ref tipada al elemento de video.
Ver solución
import { useRef } from "react";
function ReproductorSimple() {
// HTMLVideoElement expone play() y pause()
const videoRef = useRef<HTMLVideoElement>(null);
return (
<div>
<video ref={videoRef} src="/demo.mp4" width={320} />
<button onClick={() => videoRef.current?.play()}>Reproducir</button>
<button onClick={() => videoRef.current?.pause()}>Pausar</button>
{/* Nota: usamos ?. porque current es null hasta que el video monta */}
</div>
);
}