Svelte 20: untrack y tick — Funciones Utilitarias
easy⏱ 5 mincoursesvelte
untrack rompe un loop reactivo que de otro modo sería infinito
Imaginá un $effect que lee count para decidir si loguear algo, pero TAMBIÉN quiere actualizar un array history con el count actual — si esa escritura se lee reactivamente en otro lado y vuelve al mismo efecto, podés terminar con re-ejecuciones descontroladas. Envolver la lectura en untrack(() => count) le dice a Svelte 'usá este valor, pero no lo trates como razón para volver a ejecutarme' — perfecto para efectos que necesitan leer estado para llevar registro sin volverse dependientes de él.
<script>
import { untrack } from 'svelte';
let count = $state(0);
let renderCount = $state(0);
$effect(() => {
count; // tracked: effect reruns when count changes
// reading renderCount normally here would make this effect
// depend on its OWN write below -> infinite loop
const current = untrack(() => renderCount);
renderCount = current + 1;
});
</script>
tick() espera al próximo volcado del DOM
Los cambios de estado dentro de Svelte se agrupan (batch): varias asignaciones sincrónicas seguidas producen UNA sola actualización del DOM, no una por asignación, por performance. Eso significa que inmediatamente después de cambiar el estado, el DOM todavía no se puso al día. await tick() pausa tu función hasta que esa actualización pendiente del DOM realmente ocurrió, lo cual importa cuando la siguiente línea necesita leer un elemento recién renderizado (su altura, si ahora desborda, mover el foco hacia él).
<script>
import { tick } from 'svelte';
let items = $state(['a', 'b']);
let listEl;
async function addAndFocus(item) {
items.push(item);
await tick(); // wait for the new <li> to actually be in the DOM
listEl.lastElementChild?.focus();
}
</script>
Uniendo ambas: un registro de actividad con contador
La demo de abajo agrega una nueva entrada de actividad cada vez que hacés clic en un botón, y usa un $effect que mantiene un contador acumulado de 'eventos totales registrados' mediante untrack (para que el contador en sí no haga que el efecto entre en loop), mientras que un handler separado usa tick() para esperar a que el nuevo <li> se pinte antes de hacerle scroll hasta la vista y resaltarlo brevemente. Esta es deliberadamente la lección más avanzada del módulo — combina runes, timing del DOM y control de dependencias en un componente pequeño y real.
Recurrí a estas al final, no primero
untrack y tick resuelven problemas reales, pero si te encontrás necesitándolas seguido, vale la pena preguntarte si el diseño alrededor podría ser más simple — la mayoría de los componentes nunca necesitan ninguna de las dos. Guardalas en la manga para los momentos descriptos arriba: romper un efecto auto-referencial, y sincronizar trabajo imperativo del DOM con el ciclo de actualización agrupado de Svelte.