fix: correctly handle proxied signal writes before reads (#10612)

* fix: correctly handle proxied signal writes before reads

* managed

* Update packages/svelte/src/internal/client/proxy.js

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>

---------

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
pull/10614/head
Dominic Gannaway 9 months ago committed by GitHub
parent 506196b72b
commit 2de741219f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: correctly handle proxied signal writes before reads

@ -6,7 +6,8 @@ import {
updating_derived, updating_derived,
batch_inspect, batch_inspect,
current_component_context, current_component_context,
set_ignore_mutation_validation set_ignore_mutation_validation,
untrack
} from './runtime.js'; } from './runtime.js';
import { effect_active } from './reactivity/computations.js'; import { effect_active } from './reactivity/computations.js';
import { import {
@ -261,10 +262,21 @@ const state_proxy_handler = {
return has; return has;
}, },
set(target, prop, value) { set(target, prop, value, receiver) {
const metadata = target[STATE_SYMBOL]; const metadata = target[STATE_SYMBOL];
const s = metadata.s.get(prop); let s = metadata.s.get(prop);
if (s !== undefined) set(s, proxy(value, metadata.i, metadata.o)); // If we haven't yet created a source for this property, we need to ensure
// we do so otherwise if we read it later, then the write won't be tracked and
// the heuristics of effects will be different vs if we had read the proxied
// object property before writing to that property.
if (s === undefined && effect_active()) {
// the read creates a signal
untrack(() => receiver[prop]);
s = metadata.s.get(prop);
}
if (s !== undefined) {
set(s, proxy(value, metadata.i, metadata.o));
}
const is_array = metadata.a; const is_array = metadata.a;
const not_has = !(prop in target); const not_has = !(prop in target);

@ -744,6 +744,7 @@ export function get(signal) {
current_untracked_writes !== null && current_untracked_writes !== null &&
current_effect !== null && current_effect !== null &&
(current_effect.f & CLEAN) !== 0 && (current_effect.f & CLEAN) !== 0 &&
(current_effect.f & MANAGED) === 0 &&
current_untracked_writes.includes(signal) current_untracked_writes.includes(signal)
) { ) {
set_signal_status(current_effect, DIRTY); set_signal_status(current_effect, DIRTY);
@ -975,7 +976,8 @@ export function set_signal_value(signal, value) {
!ignore_mutation_validation && !ignore_mutation_validation &&
current_effect !== null && current_effect !== null &&
current_effect.c === null && current_effect.c === null &&
(current_effect.f & CLEAN) !== 0 (current_effect.f & CLEAN) !== 0 &&
(current_effect.f & MANAGED) === 0
) { ) {
if (current_dependencies !== null && current_dependencies.includes(signal)) { if (current_dependencies !== null && current_dependencies.includes(signal)) {
set_signal_status(current_effect, DIRTY); set_signal_status(current_effect, DIRTY);

@ -8,6 +8,7 @@ import {
} from '../../src/internal/client/reactivity/computations'; } from '../../src/internal/client/reactivity/computations';
import { source } from '../../src/internal/client/reactivity/sources'; import { source } from '../../src/internal/client/reactivity/sources';
import type { ComputationSignal } from '../../src/internal/client/types'; import type { ComputationSignal } from '../../src/internal/client/types';
import { proxy } from '../../src/internal/client/proxy';
/** /**
* @param runes runes mode * @param runes runes mode
@ -333,4 +334,25 @@ describe('signals', () => {
assert.equal(errored, true); assert.equal(errored, true);
}; };
}); });
test('schedules rerun when writing to signal before reading it', (runes) => {
if (!runes) return () => {};
const value = proxy({ arr: [] });
user_effect(() => {
value.arr = [];
value.arr;
});
return () => {
let errored = false;
try {
$.flushSync();
} catch (e: any) {
assert.include(e.message, 'ERR_SVELTE_TOO_MANY_UPDATES');
errored = true;
}
assert.equal(errored, true);
};
});
}); });

Loading…
Cancel
Save