Skip to content

Commit 14bf4b4

Browse files
authored
fix: ensure proxy is updated before notifying listeners (#10267)
fixes #10264 fixes #10265
1 parent 036e88f commit 14bf4b4

File tree

4 files changed

+69
-10
lines changed

4 files changed

+69
-10
lines changed

.changeset/witty-steaks-dream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: ensure proxy is updated before notifying listeners

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -244,26 +244,32 @@ const handler = {
244244
const is_array = metadata.a;
245245
const not_has = !(prop in target);
246246

247+
// variable.length = value -> clear all signals with index >= value
247248
if (is_array && prop === 'length') {
248249
for (let i = value; i < target.length; i += 1) {
249250
const s = metadata.s.get(i + '');
250251
if (s !== undefined) set(s, UNINITIALIZED);
251252
}
252253
}
253-
if (not_has) {
254-
update(metadata.v);
255-
}
254+
255+
// Set the new value before updating any signals so that any listeners get the new value
256256
// @ts-ignore
257257
target[prop] = value;
258258

259-
// If we have mutated an array directly, we might need to
260-
// signal that length has also changed too.
261-
if (is_array && not_has) {
262-
const ls = metadata.s.get('length');
263-
const length = target.length;
264-
if (ls !== undefined && ls.v !== length) {
265-
set(ls, length);
259+
if (not_has) {
260+
// If we have mutated an array directly, we might need to
261+
// signal that length has also changed. Do it before updating metadata
262+
// to ensure that iterating over the array as a result of a metadata update
263+
// will not cause the length to be out of sync.
264+
if (is_array) {
265+
const ls = metadata.s.get('length');
266+
const length = target.length;
267+
if (ls !== undefined && ls.v !== length) {
268+
set(ls, length);
269+
}
266270
}
271+
272+
update(metadata.v);
267273
}
268274

269275
return true;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { test } from '../../test';
2+
3+
/**
4+
* @type {any[]}
5+
*/
6+
let log;
7+
/**
8+
* @type {typeof console.log}}
9+
*/
10+
let original_log;
11+
12+
export default test({
13+
compileOptions: {
14+
dev: true
15+
},
16+
before_test() {
17+
log = [];
18+
original_log = console.log;
19+
console.log = (...v) => {
20+
log.push(...v);
21+
};
22+
},
23+
after_test() {
24+
console.log = original_log;
25+
},
26+
async test({ assert, target }) {
27+
const [btn] = target.querySelectorAll('button');
28+
btn.click();
29+
await Promise.resolve();
30+
31+
assert.deepEqual(log, ['init', {}, 'init', [], 'update', { x: 'hello' }, 'update', ['hello']]);
32+
}
33+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
let obj = $state({});
3+
let array = $state([]);
4+
5+
$inspect(obj);
6+
$inspect(array);
7+
</script>
8+
9+
<button
10+
onclick={() => {
11+
obj.x = "hello";
12+
array[0] = "hello";
13+
}}>
14+
add prop
15+
</button>

0 commit comments

Comments
 (0)