Can cache be manually set?

Say I have a export const getList = cache(...) and I access the list using createAsync in multiple places in the app If a user chooses to delete the entire list in some section of the app and the request succeeds, the expected result for the list is now to be empty To my understanding with the current model, the way to update the list to be empty would be to revalidate it, probably in an action, which would trigger another call to the server to get the new list (which is empty) but say I want to not go to the server to get the obvious empty-list result. Is there a way I can manually set the list to empty? I had thought I could use cache.set() to do this, but upon using it nothing seems to happen (at least reactively) I know it is frowned upon to want to manually mutate state in the UI and not re-ask the server, but I can think of a few scenarios where this is harmless and beneficial. basically, I am wondering if there is an equivalent mutate() function from createResource for createAsync
11 Replies
peerreynders
peerreynders2mo ago
I had thought I could use cache.set() to do this
The issue with this approach is that it destroys a known valid, perhaps stale value without a means to reinstate it when things go wrong and more importantly there is no way of telling that the set value is only a preliminary value.
which would trigger another call to the server
Which Start solves with Single-Flight Mutations. The universal solution is to display the preliminary value held by useSubmission while the action is in-flight. This also creates to opportunity to convey to the user that the value shown is preliminary, i.e. optimistic. Examples: https://stackblitz.com/edit/solidjs-templates-7u2yad?file=src%2Fapp.tsx https://stackblitz.com/edit/vitejs-vite-jiumjg?file=src%2FApp.tsx
peerreynders
StackBlitz
solid.js action cache interplay - StackBlitz
A Solid TypeScript project based on @solidjs/router, solid-js, typescript, vite and vite-plugin-solid
Nyi Nyi Lwin
StackBlitz
solid js - useSubmission - StackBlitz
Next generation frontend tooling. It's fast!
GitHub
solid-router/README.md at a2652b4eab6576db78e6371e5c0aa45eea85d98d ...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
Numnumberry
Numnumberry2mo ago
To my knowledge, Single Flight Mutations should be called "Single Flight Redirects" as they only save you the extra call to the server to redirect not save the extra call to get the new cache value If there is an example of using an action to mutate the value on the server, then on return mutate the cache value on the client, all in one go, then I have not seen it, and this would be my goal (to save on an unnecessary server call for a value that I already logically know) and yes I know cache.set() would be an anti-pattern, but I am simply trying to understand what it is supposed to do, because in using it based on my assumptions I dont see it doing anything (so either its bugged, or I just dont understand what it is supposed to do) and yes I could use useSubmission, but again the problem I am trying to solve is not optimistic updates necessarily, it is more so trying to stop an unnecessary call to the server
peerreynders
peerreynders2mo ago
then I have not seen it
If you'll look at saveNote on the Notes example you'll notice that there are redirects in the "use server" section. The response returned already has Location header set. That means that the Start server runtime can match the URL to the route and run the loader server side to assemble the response body and set up the single flight header to be extracted on the other end.
GitHub
solid-router/src/data/action.ts at a2652b4eab6576db78e6371e5c0aa45e...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
GitHub
solid-router/src/data/response.ts at a2652b4eab6576db78e6371e5c0aa4...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
GitHub
solid-start/examples/notes/src/routes/notes/[id]/edit.tsx at 914647...
SolidStart, the Solid app framework. Contribute to solidjs/solid-start development by creating an account on GitHub.
GitHub
solid-start/examples/notes/src/lib/api.ts at 914647c9d122cfb3dd974d...
SolidStart, the Solid app framework. Contribute to solidjs/solid-start development by creating an account on GitHub.
peerreynders
peerreynders2mo ago
what it is supposed to do
It's not clear to me that cache.set() is ever going to be documented; for the time being I'm treating it as internal.
is more so trying to stop an unnecessary call to the server
I think that largely depends on: - issuing the redirect in the action server side - having the route loader set up so that it can assemble the response body of the single-flight response just knowing the redirect URL.
Numnumberry
Numnumberry2mo ago
This was super helpful ❤️ I was using cache() but not on the server ('use server') instead I was calling an api endpoint from the client I also did not have route loaders (I thought these were just for preloading on hover) Looks like if you do not specify redirect() with revalidate then the route will invalidate all cache in use. But its still cool that all of these cache values will get their new values through the single response So in order to be more "narrow" with what is invalidated you can specify inside redirect(..., { revalidate: ... }) The only awkward thing to me is if I do not want to redirect to another page, and instead stay on the same page after the action, I seem to have to specify in redirect() to go to the page I am already on.
peerreynders
peerreynders2mo ago
I also did not have route loaders
I only realized the connection today when I started dissecting the example. It's the only possible way the server could determine what data it needs to send back, just based on the URL.
will invalidate all cache in use.
Yeah, I forgot about that …
I seem to have to specify in redirect() to go to the page I am already on.
… to convey intent better just use reload() instead (… just noticed that).
Numnumberry
Numnumberry2mo ago
it seems I was mistaken, I thought using reload and redirect were correctly doing a single flight mutation according to the logs I have on my server but I looked over to the network in the browser and it is now not single flight when I do not specify reload or redirect it correctly does single flight (but invalidates all cache) somehow I am not able to do both single flight mutation AND specify a specific cache to invalidate... maybe my keys are wrong?..
peerreynders
peerreynders2mo ago
I haven't used single flight mutations "in anger" yet, so my narrative could be off. Sounds like for some reason it decides abandon single-flight. I'd start by console.logging the route loader to see if its behaviour meets expectations on the server side. There is one inconsistency between the documentation and the code. Usually the documentation is wrong but there is a first time for everything. Try throwing the redirect/reload.
Numnumberry
Numnumberry2mo ago
https://github.com/sabercoy/solid-single-flight this summarizes what happens in 4 different scenarios 1. calling an action that does not return or throw anything will invalidate all cache in single flight 2. calling an action that throws a reload and revalidates ALL caches does not do it in single flight (each cache gets a _server call) 3. calling an action that throws a reload and revalidates ONE cache does not do it in single flight (that one cache gets a _server call) 4. calling an action that throws a redirect will redirect as normal for whatever reason, when trying to specify a specific cache to revalidate, it shrugs and says "okay, but not gonna do it in a single flight"
GitHub
GitHub - sabercoy/solid-single-flight
Contribute to sabercoy/solid-single-flight development by creating an account on GitHub.
peerreynders
peerreynders2mo ago
Compare what you have:
export const route = {
load: (args) => {
return Promise.all([
getSomeData1(),
getSomeData2(),
getSomeData3(),
getSomeData4(),
]);
},
};
export const route = {
load: (args) => {
return Promise.all([
getSomeData1(),
getSomeData2(),
getSomeData3(),
getSomeData4(),
]);
},
};
with the example
export const route = {
load({ params }) {
getNotePreview(+params.id);
}
};
export const route = {
load({ params }) {
getNotePreview(+params.id);
}
};
The example only consumes one single cache point, it's designed to load with a single request. Your observation with (1.) is a good thing because the fact that you are consuming multiple cache points doesn't break things. (2.) Should give you pause. You were trying to be helpful by being specific (while in principle duplicating 1.) but your expectations weren't met. The next thing to try:
export const mutateSomeData2 = action(async () => {
'use server';

await new Promise((res) => setTimeout(res, 1000));

console.log('ACTION 2');

// throw reload({ revalidate: [getSomeData1.key, getSomeData2.key, getSomeData3.key, getSomeData4.key] })
throw reload();
});
export const mutateSomeData2 = action(async () => {
'use server';

await new Promise((res) => setTimeout(res, 1000));

console.log('ACTION 2');

// throw reload({ revalidate: [getSomeData1.key, getSomeData2.key, getSomeData3.key, getSomeData4.key] })
throw reload();
});
… and single-flight is back. This should give rise to a new hypothesis: revalidate is an opt-out of single-flight (SF) because SF relies completely on the route loader to determine what to send back. revalidate doesn't tell SF what to put into the response body, instead it says "don't bother doing the full route load, lets instead fall back to sorting things out in the standard fashion". The trade off is that SF will tend to over-query but that's nothing unusual in the world of "stateless" servers; if server side over-query is an issue then you fall back to using more requests based on the client's assumptions. Now these trade offs may change in the future but there could be very good reasons that they currently are, what they are.
Numnumberry
Numnumberry2mo ago
I did notice this in the docs, but I figured the docs were just trying to show a minimum example, it never claimed that the load function could only return one cache, I looked at the load function like "it returns everything you need for this route" If route.load were only for one cache point, wouldnt that be very limiting? as far as conclusions from all of this I would have to go with what you stated: revalidate is an opt-out of SF and, on the flip side, SF is an opt-out of fine-grained-querying on the server side (in other words, if you want SF, you will need to make a call for everything in route.load regardless of if you want to or not) It does appear, not matter what you do, there will be over-querying in one form or another on the client or server In the end my goal was just to better understand the behaviors, limitations, and intent behind these APIs so that I could design my app accordingly. Thanks so much for helping me understand!