query function invalidates the cache every 10 clicks

I was wondering why the Suspense is triggered once in a while, then I realized query is not caching the result long enough. As you can see in this example, once every 10 click's the fetch function is hit again. The question is why query invalidates the cache basically rendering it useless. Here's the playground: https://playground.solidjs.com/anonymous/40fc1a3b-842a-4a02-8543-ebf113f9e1da
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
30 Replies
Brendonovich
Brendonovich4d ago
query's cache is short lived as it's only designed for caching preloaded data. it's not designed as a long-lived cache
gsoutz
gsoutzOP4d ago
oh ok thank you. Maybe mention that very important detail in the docs, and suggest an alternative, I used to use "cache" but the docs already says the use query instead. So what is the purpose of revalidate then if no long lived cache exists.
Brendonovich
Brendonovich4d ago
if you want to prevent the suspense from reappearing either wrap the signal update in startTransition or use aa.latest
gsoutz
gsoutzOP4d ago
can you elaborate what is happening with your suggestion because I don't understand.
Brendonovich
Brendonovich4d ago
ok technically the cache is long-lived since the data doesn't just disappear, but it's considered stale after a very short time. revalidate is so that you can refetch the data manually, since query doesn't automatically refetch when it becomes stale aa.latest will only suspend on first load, it'll use the previous value rather than re-suspending on refetch. startTransition tells Suspense to display previous content instead of the fallback
gsoutz
gsoutzOP4d ago
ok thank you very much.
peerreynders
peerreynders4d ago
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
peerreynders
peerreynders4d ago
Your example has two cache values: - query(id=1)
- query(id=2) Those cache values have reference counts and the time they were initially loaded. If the first createAsync connects to the query before the 5 second expiry then it will not rerun (de-duping for preloads). And while the reference count to query(id) remains above 0 additional createAsync connects will not rerun either. The moment the reference count goes to 0 and you are outside the intial 5 sec window it's game over- the next connect by a createAsync will cause a rerun. In your example you constantly swap between either value, letting the reference count of the other drop to 0. So once you are outside of the 5 second window it will invariably rerun. In my example I make sure the reference count never goes below 1 so it never reruns. The idea is that as long as you are holding a reference to that query value, your application is actively managing the value, so it is responsible for revalidating the query whenever an action modifies the remote value.
reload - SolidDocs
Documentation for SolidJS, the signals-powered UI framework
action - SolidDocs
Documentation for SolidJS, the signals-powered UI framework
peerreynders
peerreynders4d ago
Accesses to query automatically starts a transition if it reruns. At least that is how it worked in past. Ok, it seems that transitions are started on revalidation and on navigation. https://playground.solidjs.com/anonymous/de1ecf9e-b57b-42cc-b3c0-39e7b6927ad6
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Carl (klequis)
ok technically the cache is long-lived since the data doesn't just disappear, but it's considered stale after a very short time. revalidate is so that you can refetch the data manually, since query doesn't automatically refetch when it becomes stale
So you are saying that revalidate revalidates the 'data in the cache'? And if so, does it still only last a very short time?
Brendonovich
Brendonovich3d ago
Yeah, revalidate says 'mark this data as needing a refetch', and then any createAsync that consumes the query will re-run and cause new data to be fetched. If there is no createAsync consuming it then the re-run won't happen until there is one. The data is considered fresh only for a short time yeah
peerreynders
peerreynders3d ago
The data is considered fresh only for a short time yeah
There really is no concept of “fresh”/“stale” with query; using these terms sets the wrong expectations with people. 1. When an unreferenced query is referenced it will run. 2. When a referenced query gets additional references it will not run (for fetch de-duping). 3. Loading of a query (e.g. preload) is followed by a 5 second grace period during which referencing the unreferenced query will not re-run it again.
Brendonovich
Brendonovich3d ago
When an unreferenced query is referenced it will run.
ah is that the case?
peerreynders
peerreynders3d ago
As far as I have been able to determine by reading the query.ts code and verifying with various experiments, yes.
Brendonovich
Brendonovich3d ago
From what i can tell if a query goes from 0 to 1 references it doesn't automatically mean it'll refetch, the data is still considered 'fresh' for a short period and then when it's stale it'll refetch when going from 0 to 1 references https://playground.solidjs.com/anonymous/f33609f7-4ebf-44c2-a8e1-494c29f2abad
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Brendonovich
Brendonovich3d ago
I don't think the cache changes when it's executing in createAsync vs preload
peerreynders
peerreynders3d ago
the data is still considered 'fresh' for a short period
I'm not denying the behaviour that you are observing, I'm challenging the interpretation. It's too easy to rationalize the “5 seconds” as some “freshness” feature. It's just an initial window to give the de-duping behaviour some time to get going. By design what is preventing queries from being reloaded is that they are still being referenced; while there is at least one single reference that behaviour can go on indefinitely. Not re-running isn't linked to “freshness” but fetch de-duping.
I don't think the cache changes when it's executing in createAsync vs preload
You are correct. When a query loads it retains it's loading time no matter how the loading was initiated. However the intent behind the 5 second limit is so that pre-loading can get an early start on the fetch before the query references of the page being wired up on the client side start coming in. Could that 5 second window prevent re-runs in some other edge cases? Sure. But those edge case aren't why that limit exists.
Brendonovich
Brendonovich3d ago
Ahh that makes sense, focusing on the intent instead of the behaviour
peerreynders
peerreynders3d ago
So you are saying that revalidate revalidates the 'data in the cache'?
Revalidating is the client-side logic telling the client side router that it just did something on the server that invalidates the associated client side query values. That said the example given under revalidate is bogus:
const updateTodo = action(async (todo: Todo) => {
await putTodo(todo.id, todo);

return revalidate(getTodo.keyFor());
});
const updateTodo = action(async (todo: Todo) => {
await putTodo(todo.id, todo);

return revalidate(getTodo.keyFor());
});
revalidate is typically used outside of actions. In that particular example revalidateduplicates re-running of the query: 1. By default any action will revalidate all actively referenced querys on the page (i.e. they will all re-run). 2. If you want to narrow which querys run you have to use reload.
const updateTodo = action(async (todo: Todo) => {
await putTodo(todo.id, todo);

return reload({ revalidate: getTodo.keyFor(id) });
});
const updateTodo = action(async (todo: Todo) => {
await putTodo(todo.id, todo);

return reload({ revalidate: getTodo.keyFor(id) });
});
2b. If you are returning a result from the action use the options on json. This is with the understanding that you can only obtain that result via the result property from useSubmission. 3. The moment you narrow the revalidation keys, you opt out of single-flight (i.e. single flight wraps all the values for a route reload; unless something has changed recently).
revalidate - SolidDocs
Documentation for SolidJS, the signals-powered UI framework
Carl (klequis)
I'll need more time to digest what you guys just said but I did notice in the code that there are two timeouts that may be related to the discussion:
const PRELOAD_TIMEOUT = 5000;
const CACHE_TIMEOUT = 180000;
const PRELOAD_TIMEOUT = 5000;
const CACHE_TIMEOUT = 180000;
Do you know what the difference is? query.ts
Brendonovich
Brendonovich3d ago
PRELOAD_TIMEOUT is the amount of time data is kept around for preload use, CACHE_TIMEOUT is a minimum time to wipe unused cached data - solid router does this in the background
Carl (klequis)
So, that sounds like? 1. After PRELOAD_TIMEOUT a hover over a link won't have a cache available 2. The data stays in the cach until CACHE_TIMEOUT, and then - a) the cache is empty? - b) the cache no longer exists? I suspect the answer to this is above but I didn't get it: How can a user or Solid itself use the data during the period between PRELOAD_TIMEOUT and CACHE_TIMEOUT? Or perhaps CACHE_TIMEOUT is just a way of timing a clean-up.
gsoutz
gsoutzOP3d ago
I don't understand what you guys are talking about. Also your example is acting weird, not sure how it is supposed to work, but. Sometimes the Result: time value updates sometimes it doesn't update, each time I reload the example. and it never hit's the database again.
peerreynders
peerreynders3d ago
How can a user or Solid itself use the data during the period between PRELOAD_TIMEOUT and CACHE_TIMEOUT?
You're looking at it from a(n expiry-time based) caching perspective. Get that out of your mind. query is a fetch de-duping mechanism. While at least one createAsync is referencing the value additional references from other createAsyncs will not cause a refetch. That can go on indefinitely, there is no time limit. The moment no createAsyncs are referencing that query, the value is useless. PRELOAD_TIMEOUT is just an tiny initial window where the value is protected from reloads while the are no createAsync references. CACHE_TIMEOUT is the age when the value gets purged entirely because nobody has a reference to it. --- A fetch result held by a query wrapper is considered valid as long as the application holds at least one (createAsync) reference to it. Once the last reference to the fetch result has been dropped, the result is considered invalid; i.e. the fetch result can disposed of and for the next , “first” new reference a query re-run is required.
peerreynders
peerreynders3d ago
it doesn't update, each time I reload the example. and it never hit's the database again
That is exactly the point.
const result = createAsync(async () => {
const data = await callDb(session());
console.log('here', data);
return data;
});
const result = createAsync(async () => {
const data = await callDb(session());
console.log('here', data);
return data;
});
This one flips from id 1 to 2 and back but these:
const result1 = createAsync(() => callDb(1));
const result2 = createAsync(() => callDb(2));
const result1 = createAsync(() => callDb(1));
const result2 = createAsync(() => callDb(2));
are keeping a permanent reference to either result. This means that as far as query (a fetch de-duping mechanism) is concerned a refetch is never necessary. If you need to refetch, you use an action (which by default reloads the route data) or you revalidate(callDb.keyFor(id)). https://playground.solidjs.com/anonymous/de1ecf9e-b57b-42cc-b3c0-39e7b6927ad6 https://playground.solidjs.com/anonymous/ecfc76e8-3183-4298-8507-7aaae485025a
action - SolidDocs
Documentation for SolidJS, the signals-powered UI framework
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
gsoutz
gsoutzOP2d ago
But I don't have the list of id's 1 and 2 before hand to hardcode them like that. I don't see how this is useful to me. I just copied the example code from the real-world project. If you have a recent code repository on how to work out a real world example that would be great. https://github.com/solidjs/solid-realworld
GitHub
GitHub - solidjs/solid-realworld: A Solid Implementation of the Rea...
A Solid Implementation of the Realworld Example App - solidjs/solid-realworld
peerreynders
peerreynders2d ago
The question is why query invalidates the cache basically rendering it useless.
Your starting premise was incorrect. - query is not a caching mechanism; it exists to de-duplicate fetches. - Nothing got invalidated. Once your application de-references a fetch result, it is immediately considered “garbage”. Similarly while your application maintains a reference to the fetch result that fetch will not be repeated (indefinitely) even if it is referenced again. Your example de-references the fetch result by switching from ID 1 to 2 and back—so each time it performs a fresh fetch. It is behaving as intended. If you click it fast enough you run into an edge case. A recent query value has a 5 second window of “protection” from reloading. That time period has nothing to do with caching but it exists to enable route preloading. If you spin up a fresh “basic” SolidStart template and replace src/routes/about.tsx with this: https://discord.com/channels/722131463138705510/910635844119982080/1349086371536572437 you can see how preloading works: - Load the project on the / page - Hover over the link for the /about page. The preload will fire and the query will perform the fetch—exactly once; even though the hover event will fire multiple times. - Finally click the /about link. The app will navigate to the page and the createAsync will finally reference the query—and again there isn't another fetch because the fetch result is protected by that “5 second” window. The above describes why the “5 second” reload protection window exists. It has nothing to do with “caching”, everything with preloads working in combination with fetch de-duplication.
gsoutz
gsoutzOP2d ago
I completely understand your very detailed explanation. I think I have to keep a reference to the fetch results somewhere basically invent my own caching mechanism. Although It's not a main concern to me right now, since my app is all working local first.
peerreynders
peerreynders2d ago
If you are concerned about caching, people are usually happy with solid-query. On the other hand people who have worked with React Query often found that query's reference counting scheme was good enough but I don't think any of them were “local first”. Using dexie, dexie is your cache; what you are looking for is a sync engine.
Carl (klequis)
Carl (klequis)22h ago
Oops. I followed bad etiquette and asked a question on someone else's question. It was a good discussion, thanks!

Did you find this page helpful?