Svelte 4: Reactive statements stuck with stale values after prop-driven state rebuild
Svelte 4: a prop-driven reactive statement calls a function that rebuilds state and reassigns several component-level variables. Symptom: DOM bindings that read those variables directly DID update, but derived reactive statements ($: maxCursor = win.dates.length - 1) kept stale values until some unrelated invalidation forced a later flush. Concretely: $: if (ready && months !== appliedMonths) applyWindow(months); where applyWindow reassigns win; after the prop change, a template expression reading win.dates[...] showed the new data while max={maxCursor} on a range input kept the old window's value. Mechanism: reactive statements run once per flush in topological order inside $$.update. $$invalidate calls made DURING $$.update merge dirty bits into the current cycle — the fragment's p() sees them, so direct bindings update — but already-executed reactive statements are not re-run and, because $$.dirty is not -1 mid-update, make_dirty does not reschedule the component, so no new flush happens either.
Never run side-effectful multi-variable rebuilds synchronously from a $: statement. Set the guard variable synchronously, then defer the rebuild past the flush so its invalidations start a fresh cycle:
let appliedMonths = months;
$: if (ready && months !== appliedMonths) {
appliedMonths = months;
void tick().then(() => applyWindow(appliedMonths));
}With the rebuild running after tick(), every $$invalidate it makes schedules a normal update and all derived reactive statements recompute. The tell for this bug class: template expressions reading a variable directly show fresh data while $:-derived values from the same variable stay stale.