S
SolidJS6mo ago
alexamy

Granular update of store array doesn't trigger effects depending on the whole array

I am really puzzled with proper setup for store array management. I am creating the array in the store:
const [store, setStore] = createStore({
points: [
{ x: 50, y: 50 },
{ x: 100, y: 100 },
],
});
const [store, setStore] = createStore({
points: [
{ x: 50, y: 50 },
{ x: 100, y: 100 },
],
});
And updating elements in it (important: I can not update the whole array):
function updatePoint(i: number) {
setStore("points", i, () => ({
x: Math.random() * 200 + 20,
y: Math.random() * 200 + 20,
}));
}
function updatePoint(i: number) {
setStore("points", i, () => ({
x: Math.random() * 200 + 20,
y: Math.random() * 200 + 20,
}));
}
But neither of these effects are triggered:
createEffect(() => {
console.log('array of points', store.points);
});

createEffect(() => {
console.log('individual points', store.points[0], store.points[1]);
});
createEffect(() => {
console.log('array of points', store.points);
});

createEffect(() => {
console.log('individual points', store.points[0], store.points[1]);
});
Why these effects are not triggered and how to fix it? Demo: https://stackblitz.com/edit/solidjs-store-array-1?file=src%2FApp.tsx UPD: funny that individual points effect is working in SolidJS playround: https://playground.solidjs.com/anonymous/6e486319-8488-4651-82d2-15a7602e7afa
14 Replies
peerreynders
peerreynders6mo ago
You are likely looking for trackStore. Roughly, store's fine-grained reactivity doesn't re-run when something inside changes; it re-runs when the thing you directly accessed changes.
alexamy
alexamyOP6mo ago
I think I understand why effect based on whole array is not fired - because the reference to whole array stays the same. But why effect for each element is not fired too? Are the elements not signals?
Alex Lohr
Alex Lohr6mo ago
That's because you set an object, not a primitive, so x and y are not directly set. You could also change updatePoint to do the following:
batch(() => {
setStore('points', i, 'x', Math.random() * 200 + 20);
setStore('points', i, 'y', Math.random() * 200 + 20);
});
batch(() => {
setStore('points', i, 'x', Math.random() * 200 + 20);
setStore('points', i, 'y', Math.random() * 200 + 20);
});
That would be reactive. Or alternatively, setStore('points', i, ['x', 'y'], () => Math.random() * 200 + 20).
alexamy
alexamyOP6mo ago
@Alex Lohr It doesn't seem to work. Effects are not fired. https://stackblitz.com/edit/solidjs-store-array-1-acf9yv?file=src%2FApp.tsx
Alex Lohr
Alex Lohr6mo ago
You need to reference x and y in the effects directly for it to fire. Or use the trackStore primitive, as Peer suggested.
alexamy
alexamyOP6mo ago
Ok, thanks! Funny that individual points effect is working in SolidJS playround: https://playground.solidjs.com/anonymous/6e486319-8488-4651-82d2-15a7602e7afa
Alex Lohr
Alex Lohr6mo ago
Since you're setting those individual points, that's to be expected. It's usually simpler to wrap the setter instead of creating effects to track every single store change.
alexamy
alexamyOP6mo ago
It is the same code as in https://stackblitz.com/edit/solidjs-store-array-1?file=src%2FApp.tsx, and as in my local setup, and this effect is not triggered there, only in solidjs playground. It is the most cryptic part for me - why if I change individual point in the array, effect that depends on this individual point is not fired.
peerreynders
peerreynders6mo ago
function updatePoint(i: number) {
setStore('points', (prev) =>
prev.toSpliced(i, 1, {
x: Math.random() * 200 + 20,
y: Math.random() * 200 + 20,
})
);
}
function updatePoint(i: number) {
setStore('points', (prev) =>
prev.toSpliced(i, 1, {
x: Math.random() * 200 + 20,
y: Math.random() * 200 + 20,
})
);
}
why if I change individual point in the array, effect that depends on this individual point is not fired.
I wouldn't be surprised if it's an inherent limitation of proxies. While Arrays are objects, I find that proxied Arrays are far from perfect.
Alex Lohr
Alex Lohr6mo ago
That's true, unfortunately, especially for sparse arrays.
mdynnl
mdynnl6mo ago
this behavior of store merging top-level object properties surprises a lot of folks combined with the fact that solid playground's log has implicit deep tracking. both the old docs and new docs mention this but missing the other side of the whole story. we probably need an explainer of what gets triggered. also there's no limitation on what get triggered from reactivity standpoint. it's just a choice made solely for performance. this merging behavior can also be exploited to get the behavior op want
setStore('points', { [i]: { x: 0, y: 0 } })
setStore('points', { [i]: { x: 0, y: 0 } })
to repeat, this thread's title is the whole point of solid and what solid is capable of.
peerreynders
peerreynders6mo ago
setStore('points', { [i]: { x: 0, y: 0 } })
setStore('points', { [i]: { x: 0, y: 0 } })
So I imagine this exists so that multiple key: values can be set with a single set operation; I didn't realize that was an option; thank You!
stereokai
stereokai3mo ago
Where can I see documentation for this access syntax?
mdynnl
mdynnl3mo ago
The docs mention the shallow merging behavior. { [i]: { } } part is just JS i.e Object.assign({}, { [key]: value })
Want results from more Discord servers?
Add your server