setstore mutate more than one property in one call

Assume I have a store with a list of objects. Can I write a single call to setStore() (without produce or reconcile) that modifies two or more properties of one or more objects in the array ? In other words, if produce and reconcile already do this, how do they do it ? What do they return that enables them to mutate one object (not replace it, because it causes components with For to be recreated instead of updated) and change two or more properties in one go ? Example:
const [storeVal, setStoreVal] = createStore({
a: [
{ b: 1, c: 2 },
{ b: 3, c: 4 },
],
})
const update = () => {
setStoreVal('a', () => true, (ps, tv) => {
console.log(`prevState, traversed`, { ps, tv })
return { ...ps, b: ps.b + 1, c: ps.c + 2 }
})
}
const [storeVal, setStoreVal] = createStore({
a: [
{ b: 1, c: 2 },
{ b: 3, c: 4 },
],
})
const update = () => {
setStoreVal('a', () => true, (ps, tv) => {
console.log(`prevState, traversed`, { ps, tv })
return { ...ps, b: ps.b + 1, c: ps.c + 2 }
})
}
The code above replaces the entire object. Again, it is a problem because it causes components with <For> to be recreated. Can I mutate two or more properties in one setState call without produce() or reconcile() ? What they do under the hood ?
4 Replies
Rafael Hengles
Rafael Hengles12mo ago
Strange, I tried a small example and it did not recreate the components, I don't know yet what is causing it to recreate in my more complex app https://playground.solidjs.com/anonymous/41fd82b2-3381-46aa-b080-ff32c4afb4f0
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
high1
high112mo ago
For uses object references, and you are always creating a new object when store updates:
return { b: ps.b + 1, c: ps.c + 2 }
return { b: ps.b + 1, c: ps.c + 2 }
But here, although you are creating the new object, this object updates the keys - and if I'm not mistaken, this is where the reactivity is attached - so this essentialy properly updates the simple value that's stored under that key, and For continues to work properly. Produce works like immer, gives you an imperative way to update the previous state. You don't have to use produce or reconcile - reconcile is a way to make a lot of updates at once, without granular updating - but to retain reactivity, you would have to update by key.
setStoreVal('a', () => true, 'b', (b) => b + 1);
setStoreVal('a', () => true, 'c', (c) => c + 2);
setStoreVal('a', () => true, 'b', (b) => b + 1);
setStoreVal('a', () => true, 'c', (c) => c + 2);
should work, and you could wrap the update in batch. But it's more verbose. I think the proper way here would be to create a new array - to have the 'a' object update and map previous object to the new array to keep references. To update properly, you would need to use produce to mutate previous objects.
setStoreVal(
"a",
produce((ps) =>
ps.forEach((prev) => {
prev.b = prev.b + 1;
prev.c = prev.c + 2;
})
)
);
setStoreVal(
"a",
produce((ps) =>
ps.forEach((prev) => {
prev.b = prev.b + 1;
prev.c = prev.c + 2;
})
)
);
mdynnl
mdynnl12mo ago
{} can also be used instead of () => true
Otonashi
Otonashi12mo ago
in the reproduction linked above there should be no difference between all the alternatives suggested, including the one where the existing object isn't spread: when passing or returning an object in a setter it is always shallowly merged into the existing value if it is an object i.e. when returning the object you aren't replacing the object but setting properties on the existing object produce and reconcile work by directly accessing the underlying signals of the store so you can't easily replicate what they do unless you also want to do that, but using either one is probably more practical