Manejo de estado local con `useState` y actualización correcta.
Qué es el estado
Estado es todo dato que, al cambiar, debe repintar la pantalla. Esa es la definición operativa y el criterio para decidir qué guardar en useState y qué no.
jsx1const [count, setCount] = useState(0); 2// ↑ ↑ ↑ 3// valor setter valor inicial (solo se usa en el primer render)
Lo que hace especial al setter no es que cambie una variable: es que le avisa a React. Una variable normal cambiaría su valor y la pantalla seguiría igual, porque nadie le dijo a React que volviera a dibujar.
La regla que gobierna todo: inmutabilidad
React decide si repintar comparando referencias, no contenido. Si le pasas el mismo objeto o array que ya tenía, concluye "no cambió nada" y no repinta — aunque tú lo hayas modificado por dentro.
jsx1// ❌ MUTACIÓN: mismo array, misma referencia → React no repinta 2tareas.push(nueva); 3setTareas(tareas); 4 5// ✅ array NUEVO → React detecta el cambio 6setTareas([...tareas, nueva]);
De aquí sale el patrón de todas las operaciones sobre listas:
jsx1// Agregar 2setTareas([...tareas, nueva]); 3 4// Eliminar 5setTareas(tareas.filter((t) => t.id !== id)); 6 7// Modificar uno (¡ojo: también se copia el objeto interno!) 8setTareas(tareas.map((t) => (t.id === id ? { ...t, hecha: !t.hecha } : t)));
Ese último es el que más se falla: hacer spread del array pero mutar el objeto de adentro (t.hecha = !t.hecha) deja el mismo objeto interno, y con componentes memoizados o en modo estricto el bug reaparece.
Regla mental: en React nunca modificas lo que existe, construyes la versión nueva.
Actualizaciones por lote y el updater
setState no es instantáneo: React agrupa las actualizaciones y las aplica antes del siguiente render. Por eso esto no hace lo que parece:
jsx1setCount(count + 1); 2setCount(count + 1); // 🚨 ambas leyeron el mismo count → avanza 1, no 2
Cuando el nuevo valor depende del anterior, usa la forma de función (updater), que recibe el valor más reciente:
jsx1setCount((prev) => prev + 1); 2setCount((prev) => prev + 1); // ✅ avanza 2
La misma trampa aparece al leer el estado justo después de actualizarlo: setCount(5); console.log(count) imprime el valor viejo, porque count es una constante de este render. Si necesitas el valor nuevo, ya lo tienes: es lo que le pasaste al setter.
Reglas básicas de uso del hook
- Solo se llama en el nivel superior del componente (no dentro de loops, condicionales o funciones anidadas).
- Solo se usa dentro de componentes de React o custom hooks.
- Mantener el orden de los hooks estable evita bugs con el estado.
Inicialización perezosa
- Cuando el estado inicial es costoso, pasa una función a
useState. - React la ejecuta una sola vez en el primer render.
Estado derivado y fuente de verdad
- Evita duplicar datos que se pueden calcular desde otros estados/props.
- Prefiere un único estado "fuente de verdad" y deriva el resto en render.
Actualizaciones en lote (batching)
setStatees asíncrono; no confíes en el valor inmediato después de actualizar.- React puede agrupar múltiples actualizaciones para mejorar rendimiento.
Errores comunes a evitar
- Mutar el estado directamente (
obj.prop = ...). - Reemplazar un objeto sin conservar propiedades que necesitas.
- Usar índices como
keycuando la lista cambia de orden o se edita.
Caso práctico: Todo App
- CRUD de tareas con filtros y contador.
Ejemplos de Código
5 ejemplos
Estado básico
1const [count, setCount] = useState(0);Estado con objeto
1const [user, setUser] = useState({ nombre: "", email: "" });
2setUser((prev) => ({ ...prev, email: "test@mail.com" }));Estado con arrays
1const [items, setItems] = useState([]);
2setItems([...items, { id: Date.now(), texto: "Nueva" }]);Lazy init
1const [count, setCount] = useState(() => {
2 const saved = localStorage.getItem("count");
3 return saved ? Number(saved) : 0;
4});Toggle state
1const [isOpen, setIsOpen] = useState(false);
2setIsOpen((prev) => !prev);Recursos
3 recursos disponibles
¡Hora de Practicar!
Práctica guiada - Todo App
Práctica
Crear una Todo App con agregar, completar y eliminar tareas.
Desafío de Código
Ejercicios - useState
Ejercicios
(1) Contador con +3, (2) formulario controlado, (3) lista editable.