Resource state inconsistency during mutate calls in SolidJS
0 reputation · 14 Jul 2024, 13:21 UTC
0 reputation · 14 Jul 2024, 13:21 UTC
SolidJS provides the createResource primitive to handle asynchronous data fetching, integrating loading and error states directly into the reactivity system. When integrated with Suspense, the framework manages the rendering lifecycle based on the resolution of these resources.
The mutate function allows for manual updates to the resource state without re-triggering the underlying source function. However, there is uncertainty regarding the synchronization between a manual mutation and a pending asynchronous request triggered by a source signal change.
If mutate is called while a resource is still in a loading state from a previous trigger, the final state of the resource depends on the internal sequence tracking of the reactivity engine.
latest value tracking behave when a mutation occurs during a transition?30275 reputation · 14 Jul 2024, 19:32 UTC
In SolidJS, a manual call to mutate does not override or cancel a pending asynchronous resolution from a resource's fetcher. If a fetcher is already in flight, its eventual resolution will overwrite any manual mutations performed in the interim, provided that the fetcher's request is still considered the "latest" sequence.
The createResource primitive uses an internal sequence counter to prevent "stale" data from overwriting newer data (race conditions). However, mutate is designed for optimistic updates—it updates the current value of the resource signal immediately but does not increment the internal request sequence or invalidate the pending promise.
The likely sequence of events is as follows:
mutate(newValue) is called. The UI updates to newValue immediately.To verify this behavior in your specific environment (assuming SolidJS v1.x), you can use a delayed fetcher:
const [data, { mutate }] = createResource(async () => {
await new Promise(res => setTimeout(res, 2000));
return "Server Value";
});
// Trigger fetch, then mutate after 1 second
setTimeout(() => mutate("Optimistic Value"), 1000);
If the UI displays "Optimistic Value" for one second and then switches to "Server Value," the pending resolution has overwritten the mutation.
If you need a mutation to persist regardless of a pending request, you must ensure the source signal is updated or the fetcher is logically bypassed. For complex state synchronization, consider using a createStore to manage the "authoritative" state and using the resource solely for the initial fetch.
Diagnostic Detail Needed: Are you calling mutate as part of a user-driven action (like a form submission) that also triggers a change in the resource's source signal?
Use comments to ask for clarification. Post a solution as an answer.
30,275 reputation · 14 Jul 2024, 17:21 UTC
When you call mutate(value) without a second argument, SolidJS updates the resource’s current value but leaves the internal request‑sequence counter unchanged. Consequently, if a source‑triggered promise is still pending, its resolution will still invoke the resource’s setter and overwrite the mutation because the pending request retains the latest sequence number.
Passing a truthy second argument – e.g., mutate(value, true) or mutate(value, { revalidate: false }) – increments that internal sequence, marking the mutation as the newest “latest” value. After such a call, a pending source resolution that carries an older sequence is ignored, allowing the optimistic update to persist until the source re‑runs or you call mutate again.
This behavior is documented for SolidJS v1.6+; earlier versions lack the sequence‑mutating overload and always let the pending source win.