diff --git a/.changeset/khaki-carrots-mix.md b/.changeset/khaki-carrots-mix.md
new file mode 100644
index 000000000000..f5ef6b641881
--- /dev/null
+++ b/.changeset/khaki-carrots-mix.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure child effects are destroyed before their deriveds
diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js
index 82baeae28805..daf28eeec236 100644
--- a/packages/svelte/src/internal/client/reactivity/effects.js
+++ b/packages/svelte/src/internal/client/reactivity/effects.js
@@ -437,8 +437,8 @@ export function destroy_effect(effect, remove_dom = true) {
removed = true;
}
- destroy_effect_deriveds(effect);
destroy_effect_children(effect, remove_dom && !removed);
+ destroy_effect_deriveds(effect);
remove_reactions(effect, 0);
set_signal_status(effect, DESTROYED);
diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js
index eec8e70dc68d..e24deb5a20c2 100644
--- a/packages/svelte/src/internal/client/reactivity/props.js
+++ b/packages/svelte/src/internal/client/reactivity/props.js
@@ -1,4 +1,4 @@
-/** @import { Derived, Source } from './types.js' */
+/** @import { Source } from './types.js' */
import { DEV } from 'esm-env';
import {
PROPS_IS_BINDABLE,
@@ -12,7 +12,6 @@ import { mutable_source, set, source } from './sources.js';
import { derived, derived_safe_equal } from './deriveds.js';
import {
active_effect,
- active_reaction,
get,
is_signals_recorded,
set_active_effect,
@@ -21,7 +20,7 @@ import {
} from '../runtime.js';
import { safe_equals } from './equality.js';
import * as e from '../errors.js';
-import { BRANCH_EFFECT, DESTROYED, LEGACY_DERIVED_PROP, ROOT_EFFECT } from '../constants.js';
+import { BRANCH_EFFECT, LEGACY_DERIVED_PROP, ROOT_EFFECT } from '../constants.js';
import { proxy } from '../proxy.js';
import { capture_store_binding } from './store.js';
@@ -369,17 +368,12 @@ export function prop(props, key, flags, fallback) {
// The derived returns the current value. The underlying mutable
// source is written to from various places to persist this value.
var inner_current_value = mutable_source(prop_value);
-
var current_value = with_parent_branch(() =>
derived(() => {
var parent_value = getter();
var child_value = get(inner_current_value);
- var current_derived = /** @type {Derived} */ (active_reaction);
- // If the getter from the parent returns undefined, switch
- // to using the local value from inner_current_value instead,
- // as the parent value might have been torn down
- if (from_child || (parent_value === undefined && (current_derived.f & DESTROYED) !== 0)) {
+ if (from_child) {
from_child = false;
was_from_child = true;
return child_value;
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index 26c8d9adbba6..6bed3825c758 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -432,12 +432,12 @@ export function update_effect(effect) {
}
try {
- destroy_effect_deriveds(effect);
if ((flags & BLOCK_EFFECT) !== 0) {
destroy_block_effect_children(effect);
} else {
destroy_effect_children(effect);
}
+ destroy_effect_deriveds(effect);
execute_effect_teardown(effect);
var teardown = update_reaction(effect);
diff --git a/packages/svelte/tests/runtime-legacy/samples/props-reactive-destroy/Child.svelte b/packages/svelte/tests/runtime-legacy/samples/props-reactive-destroy/Child.svelte
new file mode 100644
index 000000000000..dc6326f8c4e8
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/props-reactive-destroy/Child.svelte
@@ -0,0 +1,11 @@
+
+
+{data ? '' : null}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-legacy/samples/props-reactive-destroy/_config.js b/packages/svelte/tests/runtime-legacy/samples/props-reactive-destroy/_config.js
new file mode 100644
index 000000000000..363c850c8bdb
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/props-reactive-destroy/_config.js
@@ -0,0 +1,10 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ test({ assert, logs, target }) {
+ target.querySelector('button')?.click();
+ flushSync();
+ assert.deepEqual(logs, ['should fire once']);
+ }
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/props-reactive-destroy/main.svelte b/packages/svelte/tests/runtime-legacy/samples/props-reactive-destroy/main.svelte
new file mode 100644
index 000000000000..00c6a5f71c93
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/props-reactive-destroy/main.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+{#if active}
+
+{/if}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/props-reactive-destroy/Child.svelte b/packages/svelte/tests/runtime-runes/samples/props-reactive-destroy/Child.svelte
new file mode 100644
index 000000000000..d16689b07d78
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-reactive-destroy/Child.svelte
@@ -0,0 +1,11 @@
+
+
+{data ? '' : null}
diff --git a/packages/svelte/tests/runtime-runes/samples/props-reactive-destroy/_config.js b/packages/svelte/tests/runtime-runes/samples/props-reactive-destroy/_config.js
new file mode 100644
index 000000000000..363c850c8bdb
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-reactive-destroy/_config.js
@@ -0,0 +1,10 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ test({ assert, logs, target }) {
+ target.querySelector('button')?.click();
+ flushSync();
+ assert.deepEqual(logs, ['should fire once']);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/props-reactive-destroy/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-reactive-destroy/main.svelte
new file mode 100644
index 000000000000..604237072ae4
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-reactive-destroy/main.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+{#if active}
+
+{/if}