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
pull/16601/head
Simon Holthausen 4 weeks ago
parent d2ba2589fe
commit 7a153cefe4

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: skip effects inside dynamic component that is about to be destroyed

@ -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));
}

@ -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

@ -0,0 +1,7 @@
<script>
let { data } = $props();
</script>
{#each data.obj.arr as i}
<p>{i}</p>
{/each}

@ -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, `<button>Change</button> <p>Comp 2</p>`);
}
});

@ -0,0 +1,16 @@
<script>
import Comp_1 from './Comp-1.svelte';
import Comp_2 from './Comp-2.svelte';
let Comp = $state.raw(Comp_1);
let data = $state.raw({ obj: { arr: [1, 2, 3] } });
function change() {
Comp = Comp_2;
data = {};
}
</script>
<button onclick={change}>Change</button>
<Comp {data} />
Loading…
Cancel
Save