diff --git a/.changeset/khaki-feet-teach.md b/.changeset/khaki-feet-teach.md new file mode 100644 index 0000000000..51b724428f --- /dev/null +++ b/.changeset/khaki-feet-teach.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: wrap `abort` in `without_reactive_context` diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index f44efa32f1..68a1555032 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -42,6 +42,7 @@ import { get_next_sibling } from '../dom/operations.js'; import { component_context, dev_current_component_function, dev_stack } from '../context.js'; import { Batch, schedule_effect } from './batch.js'; import { flatten } from './async.js'; +import { without_reactive_context } from '../dom/elements/bindings/shared.js'; /** * @param {'$effect' | '$effect.pre' | '$inspect'} rune @@ -406,7 +407,13 @@ export function destroy_effect_children(signal, remove_dom = false) { signal.first = signal.last = null; while (effect !== null) { - effect.ac?.abort(STALE_REACTION); + const controller = effect.ac; + + if (controller !== null) { + without_reactive_context(() => { + controller.abort(STALE_REACTION); + }); + } var next = effect.next; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 3d760e4b9e..22a1890e0f 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -46,6 +46,7 @@ import { Batch, batch_deriveds, flushSync, schedule_effect } from './reactivity/ import { handle_error } from './error-handling.js'; import { UNINITIALIZED } from '../../constants.js'; import { captured_signals } from './legacy.js'; +import { without_reactive_context } from './dom/elements/bindings/shared.js'; export let is_updating_effect = false; @@ -278,7 +279,10 @@ export function update_reaction(reaction) { update_version = ++read_version; if (reaction.ac !== null) { - reaction.ac.abort(STALE_REACTION); + without_reactive_context(() => { + /** @type {AbortController} */ (reaction.ac).abort(STALE_REACTION); + }); + reaction.ac = null; } diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js new file mode 100644 index 0000000000..2dacf188d7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js @@ -0,0 +1,12 @@ +import { ok, test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + async test({ assert, target, errors }) { + const btn = target.querySelector('button'); + flushSync(() => { + btn?.click(); + }); + assert.deepEqual(errors, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte new file mode 100644 index 0000000000..ebefe38fb2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte @@ -0,0 +1,24 @@ + + +{der} + + \ No newline at end of file