diff --git a/.changeset/slimy-frogs-share.md b/.changeset/slimy-frogs-share.md new file mode 100644 index 0000000000..27a393d564 --- /dev/null +++ b/.changeset/slimy-frogs-share.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: avoid false-positive batch invariant error diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index a5c9a51eec..ae4f5a6dac 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -679,7 +679,9 @@ export class Batch { batch.discard(); } } else if (sources.length > 0) { - if (DEV) { + // The microtask queue can contain the batch already scheduled to run right + // after this one is finished, so throwing the invariant would be wrong here. + if (DEV && !batch.#decrement_queued) { invariant(batch.#roots.length === 0, 'Batch has scheduled roots'); } @@ -732,7 +734,8 @@ export class Batch { } // Only apply and traverse when we know we triggered async work with marking the effects - if (batch.#roots.length > 0) { + // and know this won't run anyway right afterwards + if (batch.#roots.length > 0 && !batch.#decrement_queued) { batch.apply(); for (var root of batch.#roots) { diff --git a/packages/svelte/tests/runtime-runes/samples/async-pending-batch/_config.js b/packages/svelte/tests/runtime-runes/samples/async-pending-batch/_config.js new file mode 100644 index 0000000000..e7359efa2d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-pending-batch/_config.js @@ -0,0 +1,16 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + // This test mainly checks that we don't run into the 'Batch has scheduled roots' invariant wrongly. + // It is crafted such that two batches are scheduled to run in the same microtask, and the first + // tries to rebase the second. + async test({ assert, target }) { + await tick(); + const [run] = target.querySelectorAll('button'); + + run.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ' none none 0'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-pending-batch/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-pending-batch/main.svelte new file mode 100644 index 0000000000..19305cfb76 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-pending-batch/main.svelte @@ -0,0 +1,8 @@ + + + + +{selectedId ?? "none"} {selectedOption ?? "none"} {$effect.pending()}