En esta página
useState y Eventos
Estado en React — el concepto central
El estado es la información que un componente gestiona internamente y que puede cambiar con el tiempo. Cuando el estado cambia, React re-renderiza el componente automáticamente para reflejar el nuevo valor en la UI.
Sin estado, los componentes son estáticos (puramente presentacionales). Con estado, se vuelven interactivos y dinámicos.
import { useState } from 'react';
function Contador(): React.JSX.Element {
// Declaración: [valor actual, función para actualizar]
const [conteo, setConteo] = useState<number>(0);
return (
<div>
<p>Conteo: {conteo}</p>
<button type="button" onClick={() => setConteo(conteo + 1)}>
Incrementar
</button>
</div>
);
}useState retorna una tupla: el valor actual y una función setter. Cuando llamas al setter, React programa un re-render con el nuevo valor.
Tipado de useState con TypeScript
TypeScript puede inferir el tipo del estado desde el valor inicial, pero a veces necesitas ser explícito:
// Inferencia automática (número)
const [edad, setEdad] = useState(25);
// Explícito cuando el valor inicial es null/undefined
const [usuario, setUsuario] = useState<Usuario | null>(null);
// Arrays tipados
const [elementos, setElementos] = useState<string[]>([]);
// Union types
const [estado, setEstado] = useState<'cargando' | 'exitoso' | 'error'>('cargando');El principio de inmutabilidad
El error más común con useState es mutar el estado directamente. React usa comparación por referencia para detectar cambios: si el objeto o array es el mismo en memoria, React asume que no hubo cambios y no re-renderiza.
// ❌ INCORRECTO — mutación directa
const agregarItem = () => {
items.push('nuevo'); // Muta el array original
setItems(items); // React ve el mismo array → no re-renderiza
};
// ✅ CORRECTO — crear nuevo array
const agregarItem = () => {
setItems([...items, 'nuevo']); // Nuevo array en memoria
};
// ❌ INCORRECTO — mutar objeto
const actualizar = () => {
usuario.nombre = 'Ana'; // Muta el objeto original
setUsuario(usuario); // Misma referencia → no re-renderiza
};
// ✅ CORRECTO — crear nuevo objeto
const actualizar = () => {
setUsuario({ ...usuario, nombre: 'Ana' }); // Nuevo objeto
};La función actualizadora
Cuando el nuevo estado depende del anterior, usa la forma funcional del setter:
// ❌ Puede fallar si hay múltiples actualizaciones en el mismo ciclo
setConteo(conteo + 1);
setConteo(conteo + 1); // Ambas usan el mismo `conteo` capturado
// ✅ Siempre usa el valor más reciente del estado
setConteo((prev) => prev + 1);
setConteo((prev) => prev + 1); // Cada una recibe el prev actualizadoManejo de eventos
React usa un sistema de eventos sintéticos (SyntheticEvent) que envuelve los eventos nativos del navegador para garantizar compatibilidad cross-browser.
Eventos de ratón
import { type React } from 'react';
function AreaInteractiva(): React.JSX.Element {
const manejarClic = (e: React.MouseEvent<HTMLDivElement>) => {
console.log(`Clic en: ${e.clientX}, ${e.clientY}`);
e.stopPropagation(); // Detener propagación
};
const manejarDoble = () => {
console.log('Doble clic');
};
return (
<div
onClick={manejarClic}
onDoubleClick={manejarDoble}
onMouseEnter={() => console.log('Entró')}
onMouseLeave={() => console.log('Salió')}
>
Interactúa conmigo
</div>
);
}Eventos de teclado
function BuscadorRapido(): React.JSX.Element {
const [busqueda, setBusqueda] = useState('');
const manejarTecla = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
console.log('Buscar:', busqueda);
}
if (e.key === 'Escape') {
setBusqueda('');
}
};
return (
<input
type="search"
value={busqueda}
onChange={(e) => setBusqueda(e.target.value)}
onKeyDown={manejarTecla}
placeholder="Buscar… (Enter para buscar, Esc para limpiar)"
/>
);
}Eventos de formulario
function FormularioContacto(): React.JSX.Element {
const [datos, setDatos] = useState({ nombre: '', mensaje: '' });
const manejarSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); // Evita recarga de página
console.log('Enviando:', datos);
};
const manejarCambio = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
setDatos((prev) => ({ ...prev, [name]: value }));
};
return (
<form onSubmit={manejarSubmit}>
<input
name="nombre"
value={datos.nombre}
onChange={manejarCambio}
required
/>
<textarea
name="mensaje"
value={datos.mensaje}
onChange={manejarCambio}
required
/>
<button type="submit">Enviar</button>
</form>
);
}Múltiples estados vs un objeto de estado
Una duda frecuente: ¿usar múltiples useState independientes o un solo objeto?
// Opción A: múltiples estados independientes (preferida para valores no relacionados)
const [nombre, setNombre] = useState('');
const [email, setEmail] = useState('');
const [edad, setEdad] = useState(0);
// Opción B: objeto (preferida cuando los campos siempre cambian juntos)
const [perfil, setPerfil] = useState({ nombre: '', email: '', edad: 0 });Guía práctica: agrupa el estado que siempre se actualiza junto (como los campos de un formulario) y mantén separado el estado que cambia de forma independiente.
Estado elevado (Lifting State Up)
Cuando dos componentes hermanos necesitan compartir estado, el estado se mueve al ancestro común más cercano:
function App(): React.JSX.Element {
// Estado elevado al padre para compartir entre hijos
const [temperatura, setTemperatura] = useState(20);
return (
<div>
<SensorCelsius valor={temperatura} onChange={setTemperatura} />
<SensorFahrenheit
valor={(temperatura * 9) / 5 + 32}
onChange={(f) => setTemperatura(((f - 32) * 5) / 9)}
/>
</div>
);
}Este patrón es fundamental en React: el estado fluye hacia abajo mediante props, y los eventos fluyen hacia arriba mediante callbacks.
Inicia sesión para guardar tu progreso