Skip to content

Commit 2ad5195

Browse files
authored
fix: ensure untrack correctly retains the active reaction (#15065)
* fix: ensure untrack correctly retains the active reaction * fix: ensure untrack correctly retains the active reaction
1 parent de94159 commit 2ad5195

File tree

5 files changed

+61
-9
lines changed

5 files changed

+61
-9
lines changed

.changeset/fresh-cycles-sneeze.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: ensure untrack correctly retains the active reaction

packages/svelte/src/internal/client/reactivity/effects.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
set_is_flushing_effect,
1717
set_signal_status,
1818
untrack,
19-
skip_reaction
19+
skip_reaction,
20+
untracking
2021
} from '../runtime.js';
2122
import {
2223
DIRTY,
@@ -43,8 +44,7 @@ import * as e from '../errors.js';
4344
import { DEV } from 'esm-env';
4445
import { define_property } from '../../shared/utils.js';
4546
import { get_next_sibling } from '../dom/operations.js';
46-
import { derived, derived_safe_equal, destroy_derived } from './deriveds.js';
47-
import { legacy_mode_flag } from '../../flags/index.js';
47+
import { derived, destroy_derived } from './deriveds.js';
4848

4949
/**
5050
* @param {'$effect' | '$effect.pre' | '$inspect'} rune
@@ -166,7 +166,7 @@ function create_effect(type, fn, sync, push = true) {
166166
* @returns {boolean}
167167
*/
168168
export function effect_tracking() {
169-
if (active_reaction === null) {
169+
if (active_reaction === null || untracking) {
170170
return false;
171171
}
172172

packages/svelte/src/internal/client/reactivity/sources.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
set_derived_sources,
1818
check_dirtiness,
1919
set_is_flushing_effect,
20-
is_flushing_effect
20+
is_flushing_effect,
21+
untracking
2122
} from '../runtime.js';
2223
import { equals, safe_equals } from './equality.js';
2324
import {
@@ -148,6 +149,7 @@ export function mutate(source, value) {
148149
export function set(source, value) {
149150
if (
150151
active_reaction !== null &&
152+
!untracking &&
151153
is_runes() &&
152154
(active_reaction.f & (DERIVED | BLOCK_EFFECT)) !== 0 &&
153155
// If the source was created locally within the current derived, then

packages/svelte/src/internal/client/runtime.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ let dev_effect_stack = [];
7878
/** @type {null | Reaction} */
7979
export let active_reaction = null;
8080

81+
export let untracking = false;
82+
8183
/** @param {null | Reaction} reaction */
8284
export function set_active_reaction(reaction) {
8385
active_reaction = reaction;
@@ -423,6 +425,7 @@ export function update_reaction(reaction) {
423425
var previous_skip_reaction = skip_reaction;
424426
var prev_derived_sources = derived_sources;
425427
var previous_component_context = component_context;
428+
var previous_untracking = untracking;
426429
var flags = reaction.f;
427430

428431
new_deps = /** @type {null | Value[]} */ (null);
@@ -432,6 +435,7 @@ export function update_reaction(reaction) {
432435
skip_reaction = !is_flushing_effect && (flags & UNOWNED) !== 0;
433436
derived_sources = null;
434437
component_context = reaction.ctx;
438+
untracking = false;
435439
read_version++;
436440

437441
try {
@@ -495,6 +499,7 @@ export function update_reaction(reaction) {
495499
skip_reaction = previous_skip_reaction;
496500
derived_sources = prev_derived_sources;
497501
component_context = previous_component_context;
502+
untracking = previous_untracking;
498503
}
499504
}
500505

@@ -934,7 +939,7 @@ export function get(signal) {
934939
}
935940

936941
// Register the dependency on the current reaction signal.
937-
if (active_reaction !== null) {
942+
if (active_reaction !== null && !untracking) {
938943
if (derived_sources !== null && derived_sources.includes(signal)) {
939944
e.state_unsafe_local_read();
940945
}
@@ -1085,12 +1090,12 @@ export function invalidate_inner_signals(fn) {
10851090
* @returns {T}
10861091
*/
10871092
export function untrack(fn) {
1088-
const previous_reaction = active_reaction;
1093+
var previous_untracking = untracking;
10891094
try {
1090-
active_reaction = null;
1095+
untracking = true;
10911096
return fn();
10921097
} finally {
1093-
active_reaction = previous_reaction;
1098+
untracking = previous_untracking;
10941099
}
10951100
}
10961101

packages/svelte/tests/signals/test.ts

+40
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,46 @@ describe('signals', () => {
803803
};
804804
});
805805

806+
test('deriveds containing effects work correctly when used with untrack', () => {
807+
return () => {
808+
let a = render_effect(() => {});
809+
let b = state(0);
810+
let c;
811+
let effects = [];
812+
813+
const destroy = effect_root(() => {
814+
a = render_effect(() => {
815+
c = derived(() => {
816+
$.untrack(() => {
817+
effects.push(
818+
effect(() => {
819+
$.get(b);
820+
})
821+
);
822+
});
823+
$.get(b);
824+
});
825+
$.get(c);
826+
});
827+
});
828+
829+
assert.deepEqual(c!.children?.length, 1);
830+
assert.deepEqual(a.first, a.last);
831+
832+
set(b, 1);
833+
834+
flushSync();
835+
836+
assert.deepEqual(c!.children?.length, 1);
837+
assert.deepEqual(a.first, a.last);
838+
839+
destroy();
840+
841+
assert.deepEqual(a.deriveds, null);
842+
assert.deepEqual(a.first, null);
843+
};
844+
});
845+
806846
test('bigint states update correctly', () => {
807847
return () => {
808848
const count = state(0n);

0 commit comments

Comments
 (0)