Skip to content

Commit eebe427

Browse files
authored
fix: properly handle proxied array length mutations (#13026)
fixes #13022
1 parent e366c49 commit eebe427

File tree

3 files changed

+67
-15
lines changed

3 files changed

+67
-15
lines changed

.changeset/khaki-donkeys-jump.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: properly handle proxied array length mutations

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

+27-15
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,17 @@ export function proxy(value, parent = null, prev) {
3333
return value;
3434
}
3535

36+
/** @type {Map<any, Source<any>>} */
3637
var sources = new Map();
3738
var is_proxied_array = is_array(value);
3839
var version = source(0);
3940

41+
if (is_proxied_array) {
42+
// We need to create the length source eagerly to ensure that
43+
// mutations to the array are properly synced with our proxy
44+
sources.set('length', source(/** @type {any[]} */ (value).length));
45+
}
46+
4047
/** @type {ProxyMetadata} */
4148
var metadata;
4249

@@ -187,6 +194,22 @@ export function proxy(value, parent = null, prev) {
187194
var s = sources.get(prop);
188195
var has = prop in target;
189196

197+
// variable.length = value -> clear all signals with index >= value
198+
if (is_proxied_array && prop === 'length') {
199+
for (var i = value; i < /** @type {Source<number>} */ (s).v; i += 1) {
200+
var other_s = sources.get(i + '');
201+
if (other_s !== undefined) {
202+
set(other_s, UNINITIALIZED);
203+
} else if (i in target) {
204+
// If the item exists in the original, we need to create a uninitialized source,
205+
// else a later read of the property would result in a source being created with
206+
// the value of the original item at that index.
207+
other_s = source(UNINITIALIZED);
208+
sources.set(i + '', other_s);
209+
}
210+
}
211+
}
212+
190213
// If we haven't yet created a source for this property, we need to ensure
191214
// we do so otherwise if we read it later, then the write won't be tracked and
192215
// the heuristics of effects will be different vs if we had read the proxied
@@ -211,14 +234,6 @@ export function proxy(value, parent = null, prev) {
211234
check_ownership(metadata);
212235
}
213236

214-
// variable.length = value -> clear all signals with index >= value
215-
if (is_proxied_array && prop === 'length') {
216-
for (var i = value; i < target.length; i += 1) {
217-
var other_s = sources.get(i + '');
218-
if (other_s !== undefined) set(other_s, UNINITIALIZED);
219-
}
220-
}
221-
222237
var descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
223238

224239
// Set the new value before updating any signals so that any listeners get the new value
@@ -232,14 +247,11 @@ export function proxy(value, parent = null, prev) {
232247
// to ensure that iterating over the array as a result of a metadata update
233248
// will not cause the length to be out of sync.
234249
if (is_proxied_array && typeof prop === 'string') {
235-
var ls = sources.get('length');
236-
237-
if (ls !== undefined) {
238-
var n = Number(prop);
250+
var ls = /** @type {Source<number>} */ (sources.get('length'));
251+
var n = Number(prop);
239252

240-
if (Number.isInteger(n) && n >= ls.v) {
241-
set(ls, n + 1);
242-
}
253+
if (Number.isInteger(n) && n >= ls.v) {
254+
set(ls, n + 1);
243255
}
244256
}
245257

packages/svelte/src/internal/client/proxy.test.ts

+35
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,38 @@ test('deletes a property', () => {
9696
// deleting a non-existent property should succeed
9797
delete state.c;
9898
});
99+
100+
test('handles array.push', () => {
101+
const original = [1, 2, 3];
102+
const state = proxy(original);
103+
104+
state.push(4);
105+
assert.deepEqual(original.length, 3);
106+
assert.deepEqual(original, [1, 2, 3]);
107+
assert.deepEqual(state.length, 4);
108+
assert.deepEqual(state, [1, 2, 3, 4]);
109+
});
110+
111+
test('handles array mutation', () => {
112+
const original = [1, 2, 3];
113+
const state = proxy(original);
114+
115+
state[3] = 4;
116+
assert.deepEqual(original.length, 3);
117+
assert.deepEqual(original, [1, 2, 3]);
118+
assert.deepEqual(state.length, 4);
119+
assert.deepEqual(state, [1, 2, 3, 4]);
120+
});
121+
122+
test('handles array length mutation', () => {
123+
const original = [1, 2, 3];
124+
const state = proxy(original);
125+
126+
state.length = 0;
127+
assert.deepEqual(original.length, 3);
128+
assert.deepEqual(original, [1, 2, 3]);
129+
assert.deepEqual(original[0], 1);
130+
assert.deepEqual(state.length, 0);
131+
assert.deepEqual(state, []);
132+
assert.deepEqual(state[0], undefined);
133+
});

0 commit comments

Comments
 (0)