React te ofrece dos formas de mantener los datos vivos dentro de un componente: useState y useRef. A simple vista parecen similares. Ambos devuelven algo que puedes leer, ambos sobreviven a los re-renders y ambos te permiten recordar un valor de un clic al siguiente. Sin embargo, si eliges el incorrecto, terminarás con una pantalla que se niega a actualizarse o con un desfile interminable de renders innecesarios. La elección no es una cuestión de sintaxis. Se trata de si React necesita saberlo.

Qué hace realmente cada hook

useState es la línea oficial de comunicación de React con un componente. Cuando lo llamas, recibes un valor y una función setter. React rastrea ese valor como parte de la identidad del componente. Cuando el setter se ejecuta, React dice: "Algo ha cambiado" y programa un nuevo render para que la pantalla se actualice.

useRef, por otro lado, no es más que un objeto JavaScript simple con una propiedad current. React promete entregarte exactamente la misma referencia de objeto en cada render. No vigila lo que hay dentro. Mutar someRef.current ocurre silenciosamente. React no reaccionará.

Ese silencio es precisamente el objetivo. Los refs son una vía de escape, no un reemplazo del estado.

La división del renderizado

Si cambias el estado, el componente se vuelve a renderizar. Este es el comportamiento que la mayoría de los principiantes esperan, y es exactamente lo que quieres cuando los nuevos datos deben aparecer en pantalla. Un contador, un campo de formulario, una lista de usuarios obtenida de una API... si el usuario lo ve, probablemente pertenezca al estado. Todo el flujo de datos de React está construido en torno a la idea de que los cambios de estado notifican al renderizador para sincronizar el DOM.

Si cambias un ref, no sucede nada visualmente. La variable se actualiza de forma inmediata y sincrónica, pero el componente no se vuelve a renderizar. Esto hace que los refs sean ideales para valores que apoyan el funcionamiento interno del componente sin formar parte de la salida visual. Piensa en IDs de temporizadores, instantáneas (snapshots) de props anteriores o manejadores directos del DOM. A la interfaz de usuario (UI) no le importa el ID del intervalo que controla el autoplay de un carrusel; solo le importa qué diapositiva es visible. El ID del intervalo pertenece a un ref.

Cuándo el estado es la herramienta adecuada

Recurre a useState siempre que un valor forme parte de la superficie de tu UI.

Los campos de entrada son el ejemplo obvio. Si un usuario escribe una dirección de correo electrónico y necesitas validarla y mostrar un mensaje de error debajo del cuadro, esa cadena de texto del email necesita estado. Tanto la lógica de validación como el banner de error dependen del último valor, y React solo sabe que debe actualizar el banner porque el estado activó un re-render.

Los contadores y los interruptores (toggles) son otro elemento básico. Un botón que incrementa una puntuación, una bandera de abrir/cerrar un modal, un índice de pestañas... todo esto fluye a través del estado porque el resultado del render cambia junto con el valor. Incluso los valores derivados, como una lista filtrada que depende de una cadena de búsqueda, suelen comenzar con el estado porque el valor de origen es visible para el usuario.

También hay un matiz temporal que vale la pena entender. Las actualizaciones de estado son asíncronas y se agrupan (batching). Si llamas a setCount(count + 1) tres veces dentro de un mismo manejador de eventos, React no renderiza tres veces. Las agrupa en una única actualización. La variable count dentro de tu función en ejecución también se mantiene desactualizada hasta el siguiente render. Este agrupamiento es una característica. Mantiene las aplicaciones rápidas. Pero significa que no puedes esperar que la variable de estado refleje el nuevo valor en la línea inmediatamente siguiente.

Cuándo los refs te salvan el día

Usa useRef para la infraestructura, no