CodeForge

React 19 de Cero a Experto / Estado

Inmutabilidad: cómo React ve tus cambios

Teoría20 min20 XP

El estado real de una app son OBJETOS y ARRAYS —una lista de gastos, un formulario con muchos campos—. Actualizarlos tiene una regla de oro que ya dominas del supercurso de JavaScript: la INMUTABILIDAD. En React no es solo buena práctica: es cómo React DETECTA que algo cambió. Mutar el estado hace que React "no vea" el cambio y no repinte. Hoy cierras el círculo entre la inmutabilidad del SC-02 y el motor de React.

La dirección de la casa, otra vez

El bug: mutar no repinta

Mira el error en vivo. El botón "mal" hace push sobre el array del estado —y la UI no cambia, aunque el dato sí:

El botón "mal" SÍ agrega al array (el dato cambia), pero como es el MISMO array (misma referencia), React compara la dirección, la ve idéntica, y concluye "nada que repintar". El botón "bien" crea un array NUEVO con spread ([...gastos, "Nuevo"]), cuya referencia es distinta —y React repinta—. Es exactamente la inmutabilidad del SC-02, ahora con una razón de peso: React la NECESITA para funcionar.

Los patrones inmutables (los mismos del SC-02)

Estado complejo: objetos en el estado

Un caso muy común —un objeto de formulario en el estado— se actualiza campo por campo con spread:

objeto-estado.tsx
const [form, setForm] = useState({ nombre: "", valor: 0, categoria: "comida" });

// actualizar UN campo sin perder los demás: spread + el campo nuevo
function cambiarNombre(nuevo: string) {
setForm({ ...form, nombre: nuevo });   // copia todo, pisa 'nombre'
}

// un manejador genérico para cualquier campo:
function cambiarCampo(campo: string, valor: string) {
setForm((prev) => ({ ...prev, [campo]: valor }));   // clave dinámica (SC-02)
}

{ ...form, nombre: nuevo } copia todos los campos del formulario y pisa solo nombre —el resto sobrevive—. Sin el spread, setForm({ nombre: nuevo }) BORRARÍA valor y categoria. La clave dinámica [campo]: valor (del SC-02) te deja un manejador único para todos los campos. Para estado MUY anidado, este spread se vuelve verboso —ahí entran herramientas como Immer o el useReducer de la próxima lección—, pero el principio es siempre el mismo: copia, no mutes.

Tienes const [items, setItems] = useState([...]). Haces items.push(nuevo) y luego setItems(items). La lista en pantalla no se actualiza. ¿Por qué?

Mini-reto

Actualiza estado complejo sin mutar: 1) un estado con un array de tareas { id, texto, hecha }; 2) un botón que agregue una tarea con [...prev, nueva]; 3) botones para marcar una como hecha (map que copia y cambia solo esa con { ...t, hecha: true }) y para borrarla (filter). Usa la forma funcional prev => en todos. Comprueba que si en algún punto mutas (push, asignación directa), la UI se congela.

Qué sigue

Sabes manejar estado dentro de un componente. Pero a menudo DOS componentes necesitan compartir el mismo estado —un input y una lista, un filtro y unos resultados—. La próxima lección enseña el patrón "lifting state up": subir el estado al ancestro común para que ambos lo compartan. La clave de coordinar componentes.