From 7a153cefe49181006952519eb0634de833ad900e Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 11 Aug 2025 18:23:08 +0200 Subject: [PATCH 1/2] fix: skip effects inside dynamic component that is about to be destroyed When a dynamic component was updated to a different instance and its props were updated at the same time, effects inside the component were still called with the already-changed props. The fix is to mark the branch as skipped to never got to those effects. Fixes #16387 --- .changeset/neat-files-do.md | 5 +++++ .../client/dom/blocks/svelte-component.js | 4 +++- .../src/internal/client/reactivity/batch.js | 5 ++++- .../svelte-component-props-update/Comp-1.svelte | 7 +++++++ .../svelte-component-props-update/Comp-2.svelte | 1 + .../svelte-component-props-update/_config.js | 11 +++++++++++ .../svelte-component-props-update/main.svelte | 16 ++++++++++++++++ 7 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 .changeset/neat-files-do.md create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-1.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-2.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/main.svelte diff --git a/.changeset/neat-files-do.md b/.changeset/neat-files-do.md new file mode 100644 index 000000000000..ddba170c78d1 --- /dev/null +++ b/.changeset/neat-files-do.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: skip effects inside dynamic component that is about to be destroyed diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-component.js b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js index f16da9c42703..2697722b3953 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-component.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js @@ -62,8 +62,10 @@ export function component(node, get_component, render_fn) { if (defer) { offscreen_fragment = document.createDocumentFragment(); offscreen_fragment.append((target = create_text())); + if (effect) { + /** @type {Batch} */ (current_batch).skipped_effects.add(effect); + } } - pending_effect = branch(() => render_fn(target, component)); } diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 123bc95d163a..c6d0ad21aa73 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -11,7 +11,8 @@ import { RENDER_EFFECT, ROOT_EFFECT, USER_EFFECT, - MAYBE_DIRTY + MAYBE_DIRTY, + EFFECT_RAN } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; import { deferred, define_property } from '../../shared/utils.js'; @@ -599,6 +600,7 @@ function flush_queued_effects(effects) { if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) { var n = current_batch ? current_batch.current.size : 0; + var ran = effect.f & EFFECT_RAN; update_effect(effect); @@ -622,6 +624,7 @@ function flush_queued_effects(effects) { // if state is written in a user effect, abort and re-schedule, lest we run // effects that should be removed as a result of the state change if ( + // ran && current_batch !== null && current_batch.current.size > n && (effect.f & USER_EFFECT) !== 0 diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-1.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-1.svelte new file mode 100644 index 000000000000..fdafa27c3cd0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-1.svelte @@ -0,0 +1,7 @@ + + +{#each data.obj.arr as i} +

{i}

+{/each} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-2.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-2.svelte new file mode 100644 index 000000000000..e345a7697c6e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/Comp-2.svelte @@ -0,0 +1 @@ +

Comp 2

\ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/_config.js b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/_config.js new file mode 100644 index 000000000000..ff5ca12dbf75 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/_config.js @@ -0,0 +1,11 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [btn] = target.querySelectorAll('button'); + btn.click(); + flushSync(); + assert.htmlEqual(target.innerHTML, `

Comp 2

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/main.svelte b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/main.svelte new file mode 100644 index 000000000000..abd785fff376 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svelte-component-props-update/main.svelte @@ -0,0 +1,16 @@ + + + + + From b4be81c2817bbdabf73d03a77511ad9d87c4d3a2 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 11 Aug 2025 20:08:52 +0200 Subject: [PATCH 2/2] undo accidental commit --- packages/svelte/src/internal/client/reactivity/batch.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c6d0ad21aa73..123bc95d163a 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -11,8 +11,7 @@ import { RENDER_EFFECT, ROOT_EFFECT, USER_EFFECT, - MAYBE_DIRTY, - EFFECT_RAN + MAYBE_DIRTY } from '#client/constants'; import { async_mode_flag } from '../../flags/index.js'; import { deferred, define_property } from '../../shared/utils.js'; @@ -600,7 +599,6 @@ function flush_queued_effects(effects) { if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) { var n = current_batch ? current_batch.current.size : 0; - var ran = effect.f & EFFECT_RAN; update_effect(effect); @@ -624,7 +622,6 @@ function flush_queued_effects(effects) { // if state is written in a user effect, abort and re-schedule, lest we run // effects that should be removed as a result of the state change if ( - // ran && current_batch !== null && current_batch.current.size > n && (effect.f & USER_EFFECT) !== 0