S
SolidJS•2y ago
Mathieu

Why is this Resource fetcher called 2 times?

https://playground.solidjs.com/anonymous/347f9b68-3bae-4021-8276-00ac67404618 This log is displayed two times:
"---> why is second resource fetcher called 2 times?"
Any idea as to why?
47 Replies
Otonashi
Otonashi•2y ago
reading store.value in the setter causes it to be tracked you need to untrack reading the store
Mathieu
MathieuOP•2y ago
Is it the resource1 argument to createResource that causes that? I'm not sure to understand
Otonashi
Otonashi•2y ago
resource1 doesn't cause anything here
Otonashi
Otonashi•2y ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Otonashi
Otonashi•2y ago
untracking the store.value reads fixes it because the setter is called in a reactive scope https://playground.solidjs.com/anonymous/9972ad35-46be-4305-9a6e-66e97096c6cf
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Mathieu
MathieuOP•2y ago
@Otonashi why wasn't the fetcher called twice for resource1?
Otonashi
Otonashi•2y ago
because it doesn't have a source the setter isn't called in a reactive scope if there isn't a source because there's no need to refetch when it changes
Mathieu
MathieuOP•2y ago
but actually I need changes. But you may see in the two set of logs there are no changes. I logged the loading property and the accessor, and the logs are the same, twice.
Otonashi
Otonashi•2y ago
the change is in the value of the resource by reading the resource via store.value in the setter, the setter tracks and reruns
Mathieu
MathieuOP•2y ago
Ok but I printed the value and it doesn't change but I printed inside the resource fetcher of resource2
Otonashi
Otonashi•2y ago
resource2 not resource1 resource2 changes from undefined and it's tracked by the setter okay actually looking at it again maybe that explanation isn't right regardless it is rerunning because it's tracking itself but i need to look at the source more to understand why the case with no source doesn't have the same issue
Mathieu
MathieuOP•2y ago
I guess the core question now is why does it re-run when a resource has a source (it seems it is never desired to re-run when the source hasn't changed for that fetcher, as seen in the set of logs) it re-runs because it changes itself, this is not desired (will order my food brb)
Otonashi
Otonashi•2y ago
okay so there is another condition in that the fetcher has to be synchronous
Mathieu
MathieuOP•2y ago
the fetcher is supposed to be asynchronous call like request to server. not sure what u meant.
Otonashi
Otonashi•2y ago
well if it's async it won't run twice because it won't track the deep signal read in the setter
Mathieu
MathieuOP•2y ago
but didn't we find a code smell? and I mean for the case when the fetcher is synchronous that there is a re run
Otonashi
Otonashi•2y ago
yeah
Mathieu
MathieuOP•2y ago
and I'm thinking DX
Otonashi
Otonashi•2y ago
if it's async then it'll have left the reactive scope anyway i think my previous explanation is correct mostly the simple way of putting it is it's doing this
createEffect(() => {
source(); // track source
const v = untrack(fetcher);
setValue(() => v); // set
}
createEffect(() => {
source(); // track source
const v = untrack(fetcher);
setValue(() => v); // set
}
because if there's no source it won't create the reactive scope it'll just set immediately without creating an effect (specifically computed here) and because setValue tracks store.value it reruns when store.value changes
Mathieu
MathieuOP•2y ago
so the source adding reactivity and causing the synchronous fetcher to re-run is the "problem". Is it something to change/improve in the core?
Otonashi
Otonashi•2y ago
no the setter shouldn't be tracking in the first place arguably createResource was assuming that setting wouldn't track but anything in the shape of a signal shouldn't be tracking on sets docs issue if anything
Mathieu
MathieuOP•2y ago
but to rely on the asynchronousity of the fetcher to have an expected behavior with a custom storage like the createDeepSignal from the docs, this is more than a docs issue
Otonashi
Otonashi•2y ago
well no it doesn't rely on async it just assumes that set doesn't track that's all that's reasonable since both createSignal and createStore untrack their setters and setting something isn't reading something unless i'm explicitly reading a signal i don't expect a signal to be tracked
Mathieu
MathieuOP•2y ago
I def got re-read all we said, but what is the conclusion? I have to untrack as you have shown?
return [
() => store.value,
(v: T) =>
untrack(() => { // <- this
const unwrapped = unwrap(store.value);
typeof v === "function" && (v = v(unwrapped));
setStore("value", reconcile(v));
return store.value;
}),
] as Signal<T>;
return [
() => store.value,
(v: T) =>
untrack(() => { // <- this
const unwrapped = unwrap(store.value);
typeof v === "function" && (v = v(unwrapped));
setStore("value", reconcile(v));
return store.value;
}),
] as Signal<T>;
Otonashi
Otonashi•2y ago
yes
Mathieu
MathieuOP•2y ago
Do you think we should add that to the docs (the untrack)? But it's gonna be a little confusing though because the why is not easy to explain. Or maybe I can add an issue to see if the setter can be untracked always? If I can do anything to help I will contribute.
Otonashi
Otonashi•2y ago
add it to the docs imo
Mathieu
MathieuOP•2y ago
what about untracking systematically, in the source code
Otonashi
Otonashi•2y ago
i mean you could but you wouldn't untrack setters in your own code so i don't see why this should
Mathieu
MathieuOP•2y ago
https://playground.solidjs.com/anonymous/738a8dd3-d815-4a3e-870b-dd146c80497a Here I mutated resource1, but the fetcher of resource2 isn't re-run. But it should when the source changes.
Otonashi
Otonashi•2y ago
the tracker doesn't actually track what needs to be tracked since you're reconciling, resource1() won't change unless you change it to a non-object you need to read resource1().foo in the source since the fetcher is untracked
Mathieu
MathieuOP•2y ago
Because reconciling doesn't change the reference of the (root) object?
Otonashi
Otonashi•2y ago
basically i mean this is what you want though
Mathieu
MathieuOP•2y ago
But how do you know that so fast haha
Otonashi
Otonashi•2y ago
idk
Mathieu
MathieuOP•17mo ago
thank you! Hi @Otonashi I still have a problem with createDeepSignal. https://playground.solidjs.com/anonymous/f859f3e8-f060-4c89-9491-f31f039199c8 The problem is explained in the log. The data that the resource fetcher fetches come from a library that holds objects. When the source signal passed to createResource changes, I switch to another object from that library, but the problem is that it mutates the data held by the library. Any help would be so much appreciated (I still want to use store to benefit from nested reactivity, because this library offers to listen for changes and I update the store when there are changes with the mutate function of createResource. The problem seems to happen when I switch the object because of the source signal. But maybe there is a bigger problem with all of this that I'm missing, not sure)
Otonashi
Otonashi•17mo ago
if the shape of the data is static then you can create an initial state with that same shape, so that the first resolve doesn't get used and mutated otherwise you would have to clone the data when setting i guess, no way around it afaik
Mathieu
MathieuOP•17mo ago
I can create an initial shape if I deal with obejcts (as in my example), but what if it's an array? with a variable number of objects in the array. or maybe that would work to initialize to empty array?
Otonashi
Otonashi•17mo ago
if it's an array of objects i don't think you can avoid cloning well you could by adding objects to the array until it was at least the same size before mutating with the new data i guess but that sounds troublesome if you have nested arrays
Mathieu
MathieuOP•17mo ago
@Otonashi do you think that we could include in Solid an option "do not use the first resolve to mutate on" / "do not mutate the initial data feeded to the store"? Because I can't, I have to deep clone every time the objects. that's quite drastic
Otonashi
Otonashi•17mo ago
🤷 write a pr i mean those still require cloning afaik just by solid instead of by the user
Mathieu
MathieuOP•17mo ago
I would have to deep clone here? just to make sure I got it right:
const unwrapped = unwrap(store.value);
typeof v === 'function' && (v = v(unwrapped));
// deep clone of `v` here <--------
setStore('value', reconcile(v));
return store.value;
const unwrapped = unwrap(store.value);
typeof v === 'function' && (v = v(unwrapped));
// deep clone of `v` here <--------
setStore('value', reconcile(v));
return store.value;
Otonashi
Otonashi•17mo ago
yeah
Mathieu
MathieuOP•17mo ago
btw looking at the code again I would have done
typeof v === 'function' && (v = v(unwrap(store.value)));
typeof v === 'function' && (v = v(unwrap(store.value)));
the instruction unwrap(store.value) is only used when v is a function or maybe someone coded it like that because it's always a function here..
Otonashi
Otonashi•17mo ago
it isn't you can optimize it if you want, but unwrap is just a property access iirc
Mathieu
MathieuOP•17mo ago
still if Solid does it, wouldn't that allow less cloning? Because the user would have to clone every time before calling reconcile. Whereas if Solid clones, it may just clone the first fetching and any new objects (something along those lines)
Otonashi
Otonashi•17mo ago
yep
Want results from more Discord servers?
Add your server