fix: ensure sources within nested effects still register correctly (#16193)

* fix: ensure sources within nested effects still register correctly

When an effect creates another effect and a proxy property is read that wasn't before, the proxy will have logic where it creates the new signal in its original reaction context. But it did not have logic to correctly handle the `reaction_sources` array which prevents effects/deriveds rerunning when state created inside themselves is read and then changed inside them: Since `reaction_sources` wasn't take the reaction context into account, false positives could occur where nested effects would not register the sources as dependencies.

Fixes #15870

* oops forgot to push this
pull/16195/head
Simon H 3 months ago committed by GitHub
parent 0524d16215
commit 061ab31d23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure sources within nested effects still register correctly

@ -44,6 +44,7 @@ export function proxy(value) {
var reaction = active_reaction;
/**
* Executes the proxy in the context of the reaction it was originally created in, if any
* @template T
* @param {() => T} fn
*/

@ -138,7 +138,7 @@ export function set(source, value, should_proxy = false) {
!untracking &&
is_runes() &&
(active_reaction.f & (DERIVED | BLOCK_EFFECT)) !== 0 &&
!reaction_sources?.includes(source)
!(reaction_sources?.[1].includes(source) && reaction_sources[0] === active_reaction)
) {
e.state_unsafe_mutation();
}

@ -84,8 +84,8 @@ export function set_active_effect(effect) {
/**
* When sources are created within a reaction, reading and writing
* them should not cause a re-run
* @type {null | Source[]}
* them within that reaction should not cause a re-run
* @type {null | [active_reaction: Reaction, sources: Source[]]}
*/
export let reaction_sources = null;
@ -93,9 +93,9 @@ export let reaction_sources = null;
export function push_reaction_value(value) {
if (active_reaction !== null && active_reaction.f & EFFECT_IS_UPDATING) {
if (reaction_sources === null) {
reaction_sources = [value];
reaction_sources = [active_reaction, [value]];
} else {
reaction_sources.push(value);
reaction_sources[1].push(value);
}
}
}
@ -234,7 +234,7 @@ function schedule_possible_effect_self_invalidation(signal, effect, root = true)
for (var i = 0; i < reactions.length; i++) {
var reaction = reactions[i];
if (reaction_sources?.includes(signal)) continue;
if (reaction_sources?.[1].includes(signal) && reaction_sources[0] === active_reaction) continue;
if ((reaction.f & DERIVED) !== 0) {
schedule_possible_effect_self_invalidation(/** @type {Derived} */ (reaction), effect, false);
@ -724,7 +724,7 @@ export function get(signal) {
// Register the dependency on the current reaction signal.
if (active_reaction !== null && !untracking) {
if (!reaction_sources?.includes(signal)) {
if (!reaction_sources?.[1].includes(signal) || reaction_sources[0] !== active_reaction) {
var deps = active_reaction.deps;
if (signal.rv < read_version) {
signal.rv = read_version;

@ -1021,6 +1021,41 @@ describe('signals', () => {
};
});
test('nested effects depend on state of upper effects', () => {
const logs: number[] = [];
user_effect(() => {
const raw = state(0);
const proxied = proxy({ current: 0 });
// We need those separate, else one working and rerunning the effect
// could mask the other one not rerunning
user_effect(() => {
logs.push($.get(raw));
});
user_effect(() => {
logs.push(proxied.current);
});
// Important so that the updating effect is not running
// together with the reading effects
flushSync();
user_effect(() => {
$.untrack(() => {
set(raw, $.get(raw) + 1);
proxied.current += 1;
});
});
});
return () => {
flushSync();
assert.deepEqual(logs, [0, 0, 1, 1]);
};
});
test('proxy version state does not trigger self-dependency guard', () => {
return () => {
const s = proxy({ a: { b: 1 } });

Loading…
Cancel
Save