React
State snapshots y batching
Explica por qué el estado funciona como una snapshot por render, cómo React agrupa actualizaciones y cuándo usar funciones updater para encadenarlas correctamente.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
React
Explica por qué el estado funciona como una snapshot por render, cómo React agrupa actualizaciones y cuándo usar funciones updater para encadenarlas correctamente.
Cada render recibe una fotografía del estado. Las funciones creadas durante ese render leen esa fotografía, aunque se ejecuten después.
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // valor del render actual
}
}setCount solicita un render futuro; no cambia la variable count que ya existe en esta ejecución.
React agrupa actualizaciones relacionadas para evitar renders intermedios innecesarios.
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);Las tres leen el mismo snapshot. El resultado suele ser un incremento.
setCount((value) => value + 1);
setCount((value) => value + 1);
setCount((value) => value + 1);React procesa la cola y cada función recibe el resultado anterior.
Cuando el siguiente valor depende del anterior, especialmente dentro de varias actualizaciones o callbacks asíncronos.
setTimeout(() => {
alert(count);
}, 3000);El callback conserva el count del render que lo creó. Esto es comportamiento normal de closures, no un fallo de React.
Cada render es coherente consigo mismo. Las actualizaciones crean renders futuros y React puede agruparlas.
Cada setter añade trabajo a una cola asociada con la instancia. React procesa esa cola cuando prepara el siguiente render.
setCount(5);
setCount((current) => current + 1);
setCount(42);Conceptualmente:
snapshot inicial
↓ reemplazar por 5
5 ↓ updater +1
6 ↓ reemplazar por 42
resultado: 42Un valor directo funciona como una solicitud de reemplazo. Un updater recibe el resultado acumulado hasta ese punto.
React agrupa actualizaciones relacionadas para evitar mostrar estados intermedios incoherentes. Normalmente espera a que termine el handler antes de renderizar.
function handleCheckout() {
setIsSubmitting(true);
setError(null);
setAttemptCount((current) => current + 1);
}La interfaz no necesita mostrar cada setter por separado. React puede preparar un único render con el resultado combinado.
React no agrupa eventos intencionalmente separados de manera que rompa su semántica. Dos clicks distintos continúan siendo interacciones diferentes.
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
setTimeout(() => {
console.log(count);
}, 1000);
}
}El timeout pertenece al render que observaba count. Batching no cambia las reglas de closures. El callback seguirá viendo ese snapshot.
setCount(count + 1);
setCount(count + 1);Ambas llamadas calculan el mismo número antes de entrar en la cola.
setCount((current) => current + 1);
setCount((current) => current + 1);Cada updater calcula desde el resultado anterior. Esta es la forma correcta cuando las operaciones deben acumularse.
Después de un await, el handler sigue teniendo los valores del render que lo creó:
async function handleSave() {
const currentDraft = draft;
await saveDraft(currentDraft);
console.log(draft); // snapshot del handler
}Que React pueda agrupar actualizaciones no hace que las variables locales sean reactivas. Guarda explícitamente los datos que necesita la operación o diseña la acción alrededor de sus argumentos.
React DOM ofrece flushSync para integraciones excepcionales que necesitan que el DOM refleje una actualización inmediatamente antes de una API externa:
flushSync(() => {
setItems((current) => [...current, newItem]);
});
listRef.current?.lastElementChild?.scrollIntoView();Puede perjudicar rendimiento y forzar trabajo pendiente. No lo uses para programar UI normal; refs, effects o un diseño declarativo suelen ser mejores.
Un updater debe calcular el siguiente estado sin efectos secundarios:
setItems((current) => [...current, newItem]);No hagas requests, logs de negocio o mutaciones externas dentro. React puede ejecutar updaters más de una vez en desarrollo para comprobar pureza.
Cuando una actualización “se pierde”:
flushSync es una salida de integración, no la forma normal de actualizar.setCount(5); setCount(c => c + 1)?flushSync?Actualizar objetos y arrays sin mutación aplica estos principios a valores compuestos y referencias.