Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fix-async-reactivity-17304.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'svelte': patch
---

fix: ensure deferred effects are properly revived after async operations (fixes #17304)

When using `async` in SvelteKit with preloading data (via `data-sveltekit-preload-data` or `preloadData()`), `$effect` callbacks were not firing even though values were updating. This was because when forks are committed (which SvelteKit's preload uses), the `revive()` method wasn't ensuring the batch was properly activated before scheduling deferred effects. This fix ensures the batch context is correct when reviving effects.
30 changes: 22 additions & 8 deletions packages/svelte/src/internal/client/reactivity/batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export class Batch {

dep.f ^= WAS_MARKED;

this.#clear_marked(/** @type {Derived} */ (dep).deps);
this.#clear_marked(/** @type {Derived} */(dep).deps);
}
}

Expand Down Expand Up @@ -483,6 +483,13 @@ export class Batch {
}

revive() {
// Ensure the batch is active before scheduling effects
// This is critical for fork commits (used by SvelteKit's preload)
var was_current = current_batch === this;
if (!was_current) {
this.activate();
}

for (const e of this.#dirty_effects) {
set_signal_status(e, DIRTY);
schedule_effect(e);
Expand All @@ -496,7 +503,14 @@ export class Batch {
this.#dirty_effects = [];
this.#maybe_dirty_effects = [];

this.flush();
// Flush if there are queued root effects, or if there is no pending work left.
if (queued_root_effects.length > 0 || this.#pending === 0) {
this.flush();
}

if (!was_current) {
this.deactivate();
}
}

/** @param {() => void} fn */
Expand Down Expand Up @@ -775,14 +789,14 @@ function mark_effects(value, sources, marked, checked) {
const flags = reaction.f;

if ((flags & DERIVED) !== 0) {
mark_effects(/** @type {Derived} */ (reaction), sources, marked, checked);
mark_effects(/** @type {Derived} */(reaction), sources, marked, checked);
} else if (
(flags & (ASYNC | BLOCK_EFFECT)) !== 0 &&
(flags & DIRTY) === 0 &&
depends_on(reaction, sources, checked)
) {
set_signal_status(reaction, DIRTY);
schedule_effect(/** @type {Effect} */ (reaction));
schedule_effect(/** @type {Effect} */(reaction));
}
}
}
Expand All @@ -802,10 +816,10 @@ function mark_eager_effects(value, effects) {
const flags = reaction.f;

if ((flags & DERIVED) !== 0) {
mark_eager_effects(/** @type {Derived} */ (reaction), effects);
mark_eager_effects(/** @type {Derived} */(reaction), effects);
} else if ((flags & EAGER_EFFECT) !== 0) {
set_signal_status(reaction, DIRTY);
effects.add(/** @type {Effect} */ (reaction));
effects.add(/** @type {Effect} */(reaction));
}
}
}
Expand All @@ -825,8 +839,8 @@ function depends_on(reaction, sources, checked) {
return true;
}

if ((dep.f & DERIVED) !== 0 && depends_on(/** @type {Derived} */ (dep), sources, checked)) {
checked.set(/** @type {Derived} */ (dep), true);
if ((dep.f & DERIVED) !== 0 && depends_on(/** @type {Derived} */(dep), sources, checked)) {
checked.set(/** @type {Derived} */(dep), true);
return true;
}
}
Expand Down