diff --git a/.changeset/rare-donkeys-repair.md b/.changeset/rare-donkeys-repair.md new file mode 100644 index 0000000000..bd22c564db --- /dev/null +++ b/.changeset/rare-donkeys-repair.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent false-positive reactivity loss warning diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 32d33d6f7a..13b6e42b3e 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -181,10 +181,10 @@ export async function save(promise) { * @returns {Promise<() => T>} */ export async function track_reactivity_loss(promise) { - var previous_async_effect = reactivity_loss_tracker; + var previous_reactivity_loss_tracker = reactivity_loss_tracker; // Ensure that unrelated reads after an async operation is kicked off don't cause false positives queueMicrotask(() => { - if (reactivity_loss_tracker === previous_async_effect) { + if (reactivity_loss_tracker === previous_reactivity_loss_tracker) { set_reactivity_loss_tracker(null); } }); @@ -192,12 +192,12 @@ export async function track_reactivity_loss(promise) { var value = await promise; return () => { - set_reactivity_loss_tracker(previous_async_effect); + set_reactivity_loss_tracker(previous_reactivity_loss_tracker); // While this can result in false negatives it also guards against the more important // false positives that would occur if this is the last in a chain of async operations, // and the reactivity_loss_tracker would then stay around until the next async operation happens. queueMicrotask(() => { - if (reactivity_loss_tracker === previous_async_effect) { + if (reactivity_loss_tracker === previous_reactivity_loss_tracker) { set_reactivity_loss_tracker(null); } }); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 8d70eee43c..227203523e 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -51,6 +51,7 @@ import { batch_values, current_batch, flushSync, + previous_batch, schedule_effect } from './reactivity/batch.js'; import { handle_error } from './error-handling.js'; @@ -581,6 +582,11 @@ export function get(signal) { if ( !untracking && reactivity_loss_tracker && + // By checking that current/previous batch are null we filter out false positives. + // reactivity_loss_tracker is only reset after a microtask, so if a flush happens + // before that, we get warnings for things we shouldn't warn on. + current_batch === null && + previous_batch === null && !reactivity_loss_tracker.warned && (reactivity_loss_tracker.effect.f & REACTION_IS_UPDATING) === 0 && !reactivity_loss_tracker.effect_deps.has(signal) diff --git a/packages/svelte/tests/runtime-runes/samples/async-reactivity-loss-no-false-positive-4/_config.js b/packages/svelte/tests/runtime-runes/samples/async-reactivity-loss-no-false-positive-4/_config.js new file mode 100644 index 0000000000..4bd8adc9c5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-reactivity-loss-no-false-positive-4/_config.js @@ -0,0 +1,15 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { dev: true }, + async test({ assert, target, warnings }) { + await tick(); + const [increment] = target.querySelectorAll('button'); + + increment.click(); + await new Promise((resolve) => setTimeout(resolve, 10)); + assert.htmlEqual(target.innerHTML, ' 1 1'); + assert.deepEqual(warnings, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-reactivity-loss-no-false-positive-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-reactivity-loss-no-false-positive-4/main.svelte new file mode 100644 index 0000000000..d47d99e652 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-reactivity-loss-no-false-positive-4/main.svelte @@ -0,0 +1,16 @@ + + + + +{await x} +{y}