Ir al contenido
angel.library

Estado como snapshot, batching e inmutabilidad

  • Cada render recibe un snapshot fijo de props y estado.
  • Un setter solicita otro render; no modifica la variable que ya está ejecutándose.
  • React agrupa actualizaciones del mismo turno para evitar commits innecesarios.
  • Objetos y arrays del estado se reemplazan; no se mutan.
  • La posición y la key determinan qué estado se conserva.
function Counter() {
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1)
console.log(count) // 0: este handler pertenece al render actual
}
return <button onClick={handleClick}>{count}</button>
}

count no es una caja mutable. El handler conserva el valor del render que lo creó. setCount coloca una actualización en la cola y React ejecutará el componente con otro snapshot.

Este modelo explica los closures antiguos: un callback asíncrono sigue viendo las variables de su render, aunque la interfaz ya haya avanzado.

function notifyLater() {
const snapshot = count
window.setTimeout(() => {
alert(`El valor al programar fue ${snapshot}`)
}, 1_000)
}
function addThree() {
setCount((current) => current + 1)
setCount((current) => current + 1)
setCount((current) => current + 1)
}

React procesa la cola y entrega a cada función el resultado de la anterior. Las tres actualizaciones pueden producir un único commit con count + 3.

setCount(count + 1)
setCount(count + 1)
setCount(count + 1)

Las tres expresiones usan el mismo snapshot, por lo que solicitan el mismo valor. Usa la forma funcional cuando el valor siguiente depende del anterior; usa un valor directo cuando lo reemplazas por información ya conocida.

type Profile = {
name: string
address: {
city: string
country: string
}
}
setProfile((current) => ({
...current,
address: {
...current.address,
city: "Bogotá"
}
}))

Spread realiza una copia superficial. Copiar solo el objeto exterior y modificar address.city seguiría mutando la referencia anidada. Cuando las actualizaciones anidadas son constantes, revisa el modelo de datos, un reducer o una herramienta de actualización inmutable.

IntenciónEvita mutar conAlternativa
agregarpush, unshift[...items, item], [item, ...items]
eliminarsplice, popfilter
reemplazarasignar por índicemap o with
ordenarsort, reversetoSorted, toReversed
setTasks((current) =>
current.map((task) => (task.id === id ? { ...task, completed: true } : task))
)

React no exige inmutabilidad por estética. Las nuevas referencias permiten comparar, conservar snapshots anteriores y evitar que un render modifique datos que otro render todavía usa.

const [items, setItems] = useState<Item[]>([])
const [selectedId, setSelectedId] = useState<string | null>(null)
const selected = items.find((item) => item.id === selectedId) ?? null

Guardar también selected como objeto duplica información. Si items cambia, ambas copias pueden contradecirse. Conserva la identidad mínima y deriva el resto durante el render.

React relaciona componentes por tipo, posición y key:

{
mode === "company" ? (
<TaxForm
key="company"
mode="company"
/>
) : (
<TaxForm
key="person"
mode="person"
/>
)
}

Cambiar la key indica que es otra identidad y reinicia su estado. Sin keys distintas, React puede conservar la misma instancia en esa posición.

No declares un componente dentro de otro componente:

function Parent() {
function Child() {
return <input />
}
return <Child />
}

Cada render crea otro tipo Child, por lo que su estado puede reiniciarse. Declara los tipos de componente en el nivel del módulo.

PreguntaConsecuencia
¿solo un componente lo usa?estado local
¿dos hermanos deben coordinarse?subir al ancestro común
¿debe sobrevivir recarga o compartirse por enlace?URL, storage o servidor
¿proviene del servidor?caché de datos, no copia global arbitraria
¿se calcula desde otras entradas?derivarlo, no almacenarlo

Conserva un borrador local mientras se edita, pero reinícialo cuando cambia la entidad:

function EditorRoute({ product }: { product: Product }) {
return (
<ProductForm
key={product.id}
initialProduct={product}
/>
)
}

La key expresa que editar otro producto inicia otra sesión de formulario. Es más claro que un efecto dedicado a copiar cada prop nueva al estado.

  • Efectos que sincronizan dos estados locales.
  • muchos booleanos capaces de formar combinaciones imposibles.
  • copiar una prop a estado en cada cambio.
  • objetos profundamente anidados que requieren spreads repetidos.
  • keys aleatorias para “forzar” renders.
  • una store global usada para valores temporales de un componente.