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}