diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c60bf8bbda..58c382b71f 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -1291,6 +1291,7 @@ export function eager(fn) { let parent = active_reaction; let version = version_map.get(parent) ?? source(0); + version.f |= EAGER_EFFECT; version_map.set(parent, version); if (DEV) { @@ -1308,12 +1309,15 @@ export function eager(fn) { // the first time this runs, we create an eager effect // that will run eagerly whenever the expression changes var previous_batch_values = batch_values; + var previous_stale_sources = stale_sources; try { batch_values = null; + stale_sources = null; value = fn(); } finally { batch_values = previous_batch_values; + stale_sources = previous_stale_sources; } return; diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index d3f2b51058..c39184427f 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -354,14 +354,15 @@ export function increment(source) { * Make `reaction` re-run in the current batch. For a derived this means dirtying * its reactions, as if the derived's value had changed. * @param {Reaction} reaction + * @param {number} status */ -export function invalidate(reaction) { - set_signal_status(reaction, DIRTY); +export function invalidate(reaction, status) { + set_signal_status(reaction, status); if ((reaction.f & DERIVED) !== 0) { seen = null; count_deps = 0; - mark_reactions(/** @type {Derived} */ (reaction), DIRTY, null); + mark_reactions(/** @type {Derived} */ (reaction), status, null); seen = null; } else { schedule_effect(/** @type {Effect} */ (reaction)); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 14643b3224..f39a3760be 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -23,7 +23,8 @@ import { ERROR_VALUE, MANAGED_EFFECT, REACTION_RAN, - ASYNC + ASYNC, + EAGER_EFFECT } from './constants.js'; import { invalidate, old_values } from './reactivity/sources.js'; import { @@ -790,17 +791,20 @@ function get_batch_value(signal, first_time) { batch.stale_readers.add(reader); if (current.is_eager) { - // TODO only do this if we can see that the batch doesn't have this already scheduled in (maybe)dirty effects. - batch.oncommit(() => { - batch.stale_readers.delete(reader); - Batch.ensure(); - invalidate(reader); - }); + // Reactions of eager effects with at least one non-eager effect (which we know is true here due to the stale_sources check above) + // need to rerun because they could do something like `$state.eager(count) !== count`. + if (reader.deps?.some((dep) => dep.f & EAGER_EFFECT)) { + batch.oncommit(() => { + batch.stale_readers.delete(reader); + Batch.ensure(); + invalidate(reader, DIRTY); + }); + } } else { queue_micro_task(() => { batch.stale_readers.delete(reader); const b = batch.activate(); - invalidate(reader); + invalidate(reader, MAYBE_DIRTY); b.flush(); }); } diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring-2/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring-2/_config.js new file mode 100644 index 0000000000..03b8c3ffc8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring-2/_config.js @@ -0,0 +1,38 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + await tick(); + + const [increment_a, increment_a_b, shift_second, shift] = target.querySelectorAll('button'); + const [p] = target.querySelectorAll('p'); + + increment_a.click(); + await tick(); + assert.deepEqual(logs, ['a_d: 1']); + logs.length = 0; + + increment_a_b.click(); + await tick(); + assert.deepEqual(logs, ['a_d: 2', 'c: 1']); + logs.length = 0; + + shift_second.click(); // a_d of second batch + await tick(); + assert.deepEqual(logs, ['c: 3']); + logs.length = 0; + + shift.click(); // a_d of first batch - should not rerun second batch + await tick(); + assert.deepEqual(logs, ['c: 1']); + logs.length = 0; + + shift.click(); + await tick(); + shift.click(); + await tick(); + assert.htmlEqual(p.innerHTML, '3'); + assert.deepEqual(logs, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring-2/main.svelte new file mode 100644 index 0000000000..f327702493 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-not-overfiring-2/main.svelte @@ -0,0 +1,21 @@ + + + + + + + +

{c}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-eager-block/_config.js b/packages/svelte/tests/runtime-runes/samples/async-eager-block/_config.js index b6d283c8a5..d1763b7c03 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-eager-block/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-eager-block/_config.js @@ -2,7 +2,7 @@ import { tick } from 'svelte'; import { test } from '../../test'; export default test({ - async test({ assert, target }) { + async test({ assert, target, logs }) { await tick(); const [increment, resolve] = target.querySelectorAll('button'); @@ -10,14 +10,15 @@ export default test({ await tick(); assert.htmlEqual( target.innerHTML, - ' 0

loading...

' + ' 0

loading...

1

' //

0

would also be ok here ); resolve.click(); await tick(); assert.htmlEqual( target.innerHTML, - ' 1

1

' + ' 1

1

1

' ); + assert.deepEqual(logs, [0, 1]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-eager-block/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-eager-block/main.svelte index 61f4705d06..8742a34ec5 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-eager-block/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-eager-block/main.svelte @@ -12,9 +12,14 @@ -{await delay(count)} +{await delay(count)} + {#if $state.eager(count) !== count}

loading...

{:else}

{count}

{/if} + +{#if $state.eager(console.log(count) || count)} +

{count}

+{/if}