diff --git a/.changeset/nasty-clocks-exercise.md b/.changeset/nasty-clocks-exercise.md
new file mode 100644
index 000000000000..7362f8c0b25d
--- /dev/null
+++ b/.changeset/nasty-clocks-exercise.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: corrects a beforeUpdate/afterUpdate bug
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index a67f300b219a..30807a317a18 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -960,10 +960,18 @@ export function set_signal_value(signal, value) {
schedule_effect(current_effect, false);
}
mark_signal_consumers(signal, DIRTY, true);
- // If we have afterUpdates locally on the component, but we're within a render effect
- // then we will need to manually invoke the beforeUpdate/afterUpdate logic.
+ // This logic checks if there are any render effects queued after the above marking
+ // of consumers. If there are render effects that have the same component context as
+ // the source signal we're writing to, then we can bail-out of this logic as there
+ // will be a render effect in the queue that hopefully takes case of triggering the
+ // beforeUpdate/afterUpdate logic (doing it again here would duplicate them). However,
+ // if the render effects scheduled in the queue are unrelated to the component context,
+ // then we need to trigger the beforeUpdate/afterUpdate logic here instead.
// TODO: should we put this being a is_runes check and only run it in non-runes mode?
- if (current_effect === null && current_queued_pre_and_render_effects.length === 0) {
+ if (
+ current_effect === null &&
+ current_queued_pre_and_render_effects.every((e) => e.context !== component_context)
+ ) {
const update_callbacks = component_context?.update_callbacks;
if (update_callbacks != null) {
update_callbacks.before.forEach(/** @param {any} c */ (c) => c());
diff --git a/packages/svelte/tests/runtime-runes/samples/runes-before-after-update/Child.svelte b/packages/svelte/tests/runtime-runes/samples/runes-before-after-update/Child.svelte
new file mode 100644
index 000000000000..5f27f07c9dda
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/runes-before-after-update/Child.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/runes-before-after-update/_config.js b/packages/svelte/tests/runtime-runes/samples/runes-before-after-update/_config.js
new file mode 100644
index 000000000000..5a357f88c425
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/runes-before-after-update/_config.js
@@ -0,0 +1,15 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ html: '',
+
+ async test({ assert, target, component }) {
+ const [btn] = target.querySelectorAll('button');
+ flushSync(() => {
+ btn.click();
+ });
+ assert.deepEqual(component.log, ['beforeUpdate', 'afterUpdate']);
+ assert.htmlEqual(target.innerHTML, ``);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/runes-before-after-update/main.svelte b/packages/svelte/tests/runtime-runes/samples/runes-before-after-update/main.svelte
new file mode 100644
index 000000000000..b3e6f583fda8
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/runes-before-after-update/main.svelte
@@ -0,0 +1,22 @@
+
+
+