S
SolidJS•3mo ago
stereokai

Template doesn't react to property change in store of array of objects

Using either of the 2 methods, when I log the users store, I see that the name of the user has changed in the store object. However, the template doesn't update/react to the new name. Why is that?
// user: { name: string }

const [users, setUsers] = createStore([{ name: "some user" }]);
const [userName, setUserName] = createSignal("");

currUser = users[0];
setUserName("newName")

// Store update, method 1:
setUsers((user) => user === currUser, "name", userName());
// Store update, method 2:
setUsers(
produce((users) => {
const user = users.find((user) => user === currUser);
if (user) {
user.name = userName();
}
}),
);

// JSX:
<For each={users}>
{(user, index) => {
return (
<div>
{`${user.name}`}
</div>
);
}}
</For>
// user: { name: string }

const [users, setUsers] = createStore([{ name: "some user" }]);
const [userName, setUserName] = createSignal("");

currUser = users[0];
setUserName("newName")

// Store update, method 1:
setUsers((user) => user === currUser, "name", userName());
// Store update, method 2:
setUsers(
produce((users) => {
const user = users.find((user) => user === currUser);
if (user) {
user.name = userName();
}
}),
);

// JSX:
<For each={users}>
{(user, index) => {
return (
<div>
{`${user.name}`}
</div>
);
}}
</For>
26 Replies
Alex Lohr
Alex Lohr•3mo ago
The problem is that the reference for currUser may not match. if you used setUsers(0, "name", userName()), it would work AFAICT.
stereokai
stereokaiOP•3mo ago
However, I can see the change in the store object itself
Alex Lohr
Alex Lohr•3mo ago
That is because currUser does not need to be reference-identical to the previous object to show the change. Objects in stores are not ref-safe.
stereokai
stereokaiOP•3mo ago
But I am not replacing any objects? I am sorry but I'm not sure I fully understand what you're trying to say. Using both store update methods, inspecting the store object reveals that the name key on the user object indeed updated as expected. However the template does not reflect the change.
Alex Lohr
Alex Lohr•3mo ago
The problem is that the users[0] you read at the start is the result of a proxy operation.
stereokai
stereokaiOP•3mo ago
What does ref-safe mean? Just so we speak the same language 🙂 Thanks
Alex Lohr
Alex Lohr•3mo ago
Objects are stored by their reference on the stack. {} == {} // false here you can see that two empty objects have different references.
stereokai
stereokaiOP•3mo ago
But this function (user) => user === currUser, which is used in both methods, does match the object by reference.
Alex Lohr
Alex Lohr•3mo ago
That's the issue. user is the user from the underlying object, currUser is from the proxy.
stereokai
stereokaiOP•3mo ago
I appreciate your help but it's too terse for me to fully grasp your point. Is the fact that they are matching a problem?
Alex Lohr
Alex Lohr•3mo ago
A Proxy is able to wrap objects and interject the access on the object. It inherently has a different reference than the underlying object. The problem is that you attempt to compare the reference of a proxy object with the underlying object, which will never match, so the amount of users selected is neccessarily empty. Solid's stores are based on Proxies to intercept reading and allow to track the access inside effects and memos.
Alex Lohr
Alex Lohr•3mo ago
If you want to have unique user names, maybe try a ReactiveSet<string> instead: https://primitives.solidjs.community/package/set#reactiveset
Solid Primitives
A library of high-quality primitives that extend SolidJS reactivity
stereokai
stereokaiOP•3mo ago
@Alex Lohr Thanks, but the actual object is a bigger class. My code example is just a simplification of the pattern. However the essentials are identical 🙂 Below is a screen shot of a debugger on the code. As you can see, the 2 objects are matching in the find callback (you can see Return value: true on the right). And as I explained, when inspecting the store, I do see the updated user name
stereokai
stereokaiOP•3mo ago
No description
REEEEE
REEEEE•3mo ago
If they're classes, they aren't reactive by default because Solid doesn't automatically wrap class properties to make them reactive
stereokai
stereokaiOP•3mo ago
@REEEEE you mean each user, right? Yes, each user is a class. How could I get around this limitation?
REEEEE
REEEEE•3mo ago
well if there's only some properties of the class that need to be reactive, you can make them signals. If you want to make all properties of a class reactive, you can do
class MyClass {
constructor(){
return createMutable(this)
}
}
class MyClass {
constructor(){
return createMutable(this)
}
}
stereokai
stereokaiOP•3mo ago
Thanks. That's creative. Possible to wrap a signal with a getter and setter right?
REEEEE
REEEEE•3mo ago
you would have to do something like this
class MyClass{
_prop = createSignal(0)

get prop(){
return this._prop[0]()
}
set prop(nextValue){
return this._prop[1](nextValue)
}
}
class MyClass{
_prop = createSignal(0)

get prop(){
return this._prop[0]()
}
set prop(nextValue){
return this._prop[1](nextValue)
}
}
If you use createMutable, it will work automatically
stereokai
stereokaiOP•3mo ago
I see. That's cool. However this class also has some more complex objects and arrays as props. Is it possible to explicitly exclude them from createMutable? Or is it all-inclusive
REEEEE
REEEEE•3mo ago
At the moment it's all inclusive, you can take a look at this conversation and implementation instead https://discord.com/channels/722131463138705510/990790895244808192/1285379168602951793
stereokai
stereokaiOP•3mo ago
@REEEEE Thank you very much, that's an interesting link. Just been experimenting with createMutable, what would be the correct way to locate the current user for updating - assuming there's no id or index? Now they are in fact not matching anymore, as @Alex Lohr expected.
produce((users) => {
const user = users.find((user) => user === currUser);
if (user) {
user.name = userName();
}
}),
produce((users) => {
const user = users.find((user) => user === currUser);
if (user) {
user.name = userName();
}
}),
REEEEE
REEEEE•3mo ago
Assuming user and currUser and both the same underlying class instance, they should match I would imagine but I suppose the introduction of the proxy would break it. You would probably need a unique identifier for them in that case This is partly where createMutable breaks rather than creating individual signals per property
stereokai
stereokaiOP•3mo ago
Got it. Since at the moment the class has only 2 properties that warrant reactivity I'm opting for the direct getter/setter method. But I'll definitely keep this lesson in mind. Thanks!
Alex Lohr
Alex Lohr•3mo ago
There is a way, you can access the underlying object using unwrap: https://docs.solidjs.com/reference/store-utilities/unwrap
stereokai
stereokaiOP•3mo ago
@Alex Lohr that's right, I'm even using it somewhere else. Thanks!
Want results from more Discord servers?
Add your server