From bfbb026f2f7db6ced0d86ba0feb40587c0e8f598 Mon Sep 17 00:00:00 2001 From: JY Wey <34165386+JaiWey@users.noreply.github.com> Date: Sat, 11 Jul 2026 01:42:01 +1200 Subject: [PATCH] fix: skip unnecessary derived effect in earlier batch (#18525) fixes #18438 Currently the `mark` method inside `#merge` for `earlier_batch` will schedule effect for undirty derived. Add the condition for derived to prevent unnecessary effect be scheduled. ``` if ((flags & DERIVED) !== 0) { mark(/** @type {Derived} */ (reaction)); } ``` --- .changeset/curly-wasps-hide.md | 5 ++++ .../src/internal/client/reactivity/batch.js | 6 ++++ .../samples/async-batch-derived/_config.js | 28 ++++++++++++++++++ .../samples/async-batch-derived/main.svelte | 29 +++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 .changeset/curly-wasps-hide.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-batch-derived/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-batch-derived/main.svelte diff --git a/.changeset/curly-wasps-hide.md b/.changeset/curly-wasps-hide.md new file mode 100644 index 0000000000..7e55d77ba0 --- /dev/null +++ b/.changeset/curly-wasps-hide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: skip unnecessary derived effect in earlier batch diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 7d14b80519..5becae2dc9 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -530,6 +530,12 @@ export class Batch { const mark = (value) => { var reactions = value.reactions; if (reactions === null) return; + // skip if value is derived and is neither dirty nor maybe dirty. transitive + // deriveds (a derived depending on another derived) are only MAYBE_DIRTY, so + // we must continue traversing them to reach the effects that depend on them + if ((value.f & DERIVED) !== 0 && (value.f & (DIRTY | MAYBE_DIRTY)) === 0) { + return; + } for (const reaction of reactions) { var flags = reaction.f; diff --git a/packages/svelte/tests/runtime-runes/samples/async-batch-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/async-batch-derived/_config.js new file mode 100644 index 0000000000..d5286d9d95 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-batch-derived/_config.js @@ -0,0 +1,28 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [increment, pop] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + `
Loading...
` + ); + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + `Loading...
` + ); + pop.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ` 2 2 1`); + + pop.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ` 2 2 1`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-batch-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-batch-derived/main.svelte new file mode 100644 index 0000000000..af7f0f468c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-batch-derived/main.svelte @@ -0,0 +1,29 @@ + + + + +{#snippet defaultPending()} +Loading...
+{/snippet} + +{#if count > 0} +