Skip to content
\n

As you can probably expect either endpoint will return their cached query, but if you go to the other page it will also start responding with the other query.

\n

After requesting both endpoints, both endpoints will now return this JSON:

\n
{\n\t\"queries\": [{\n\t\t\"config\": {},\n\t\t\"queryKey\": [\"a\"],\n\t\t\"updatedAt\": 1599832897654,\n\t\t\"data\": \"A\"\n\t}, {\n\t\t\"config\": {},\n\t\t\"queryKey\": [\"b\"],\n\t\t\"updatedAt\": 1599832895728,\n\t\t\"data\": \"B\"\n\t}]\n}
\n

This doesn't matter if your response is just B, but if you have a really big app, like we do, which also runs for different countries and markets, we need to filter this in some way. At the moment we have something like this to filter it per country and locale:

\n
import express from \"express\";\nimport { makeQueryCache, QueryCache, QueryKey } from \"react-query\";\nimport { dehydrate } from \"react-query/hydration\";\n\nconst queryCache: QueryCache = makeQueryCache();\n\nconst server = express();\n\ntype LocalizedParams = QueryKey & {\n  country: string;\n};\n\nconst dehydrateCache = (country: string) => {\n  return dehydrate(queryCache, {\n    shouldDehydrate: (query) => {\n      const [, params] = query.queryKey;\n\n      if (params && (params as LocalizedParams).country === country) {\n        return true;\n      }\n\n      return false;\n    },\n  });\n};\n\nserver.get(\"/a\", async (req, res) => {\n  await queryCache.prefetchQuery([\"a\", { country: \"US\" }], () =>\n    Promise.resolve(\"A\")\n  );\n\n  res.send(dehydrateCache(\"US\"));\n});\n\nserver.get(\"/b\", async (req, res) => {\n  await queryCache.prefetchQuery([\"b\", { country: \"DE\" }], () =>\n    Promise.resolve(\"B\")\n  );\n\n  res.send(dehydrateCache(\"DE\"));\n});\n\nserver.listen(3000);
\n

As you can see this already becomes a bit more complex, we need to use the shouldDehydrate function (which is a great API in itself by the way) to filter the queries. All the queries now need to have the same signature and it would break if someone uses a query that is just a string, or maybe is the same for all countries.

\n

Possible solution

\n

I hope someone else thought of something more elegant for this, but my idea would be to do something like this:

\n
import express from \"express\";\nimport { makeQueryCache, QueryCache, QueryKey } from \"react-query\";\nimport { dehydrate } from \"react-query/hydration\";\nimport deepEqual from \"fast-deep-equal\";\n\nconst queryCache: QueryCache = makeQueryCache();\n\nconst server = express();\n\nlet requestLog: QueryKey[] = [];\n\nconst dehydrateCache = () => {\n  return dehydrate(queryCache, {\n    shouldDehydrate: (query) => {\n      const hasBeenRequested = requestLog.find((params) => {\n        return deepEqual(params, query.queryKey);\n      });\n\n      return !!hasBeenRequested;\n    },\n  });\n};\n\nserver.use((req, res, next) => {\n  requestLog = [];\n\n  next();\n});\n\nserver.get(\"/a\", async (req, res) => {\n  const params = [\"a\", { country: \"US\" }];\n  requestLog.push(params);\n  await queryCache.prefetchQuery(params, () => Promise.resolve(\"A\"));\n\n  res.send(dehydrateCache());\n});\n\nserver.get(\"/b\", async (req, res) => {\n  const params = [\"b\", { country: \"US\" }];\n  requestLog.push(params);\n  await queryCache.prefetchQuery(params, () => Promise.resolve(\"B\"));\n\n  res.send(dehydrateCache());\n});\n\nserver.listen(3000);
\n

In this example I need to keep a log of all the requests per request handler. Then when I dehydrate I check which requests were done and I reset the list of requests every time a request happens.

\n

@tannerlinsley any ideas, maybe this could be something that's integrated inside react-query?

","upvoteCount":2,"answerCount":2,"acceptedAnswer":{"@type":"Answer","text":"

Thanks for the feedback, this is an interesting use case! So what you need is a way to keep track of which queries were fetched in this particular request so that you can dehydrate only those? Sounds like a very classic SSR problem. 😁

\n

I think the solution you came up with is a good one. 😄 I guess what you could do to simplify it a bit is to wrap prefetchQuery with your own function that takes care of the requestLog-stuff behind the scenes.

\n

A slightly different/expanded approach that could be generalized into a third party library would be to create a kind of full proxy for that shared cache, and in that proxy keep track of which queries has been accessed. Something like const proxyCache = createProxyCache(queryCache) and expose a proxyCache.usedQueryKeys from that proxy. All methods on that proxy would work as normal and towards the original cache, with the addition that they keep track of the query keys used. The only thing I think we could do differently than this in React Query directly is to make dehydrate automatically only dehydrate the queries that where used by the proxy cache instead of exposing a proxyCache.usedQueryKeys.

\n

While definitely a great case, my initial feeling is that since the hydration APIs are new, we want to keep the API surface as small as possible to leave ourselves room to figure out the best patterns to support going forward. I think there are probably a bunch of cool things we could do to better support shared caches in the future. 😄

\n

An example idea for the future. Maybe you could take the concept above and pair it with some way of marking queries \"shared\"? Any queries not explicitly marked \"shared\" would only be cached in the \"proxy\" (which now is something other than a proxy) and thus exist for that request only, while any queries marked \"shared\" would be cached in the original shared cache. It's because of ideas like this I'm hesitant implementing stuff too early before we've figured out the bigger picture, I especially think there is a lot of valuable feedback and experimentation people like you and others in the ecosystem can help provide to form that!

","upvoteCount":1,"url":"https://github.com/TanStack/query/discussions/998#discussioncomment-77104"}}}

Server-side shared query cache #998

Answered by Ephem
PepijnSenders asked this question in General
Discussion options

You must be logged in to vote

Thanks for the feedback, this is an interesting use case! So what you need is a way to keep track of which queries were fetched in this particular request so that you can dehydrate only those? Sounds like a very classic SSR problem. 😁

I think the solution you came up with is a good one. 😄 I guess what you could do to simplify it a bit is to wrap prefetchQuery with your own function that takes care of the requestLog-stuff behind the scenes.

A slightly different/expanded approach that could be generalized into a third party library would be to create a kind of full proxy for that shared cache, and in that proxy keep track of which queries has been accessed. Something like const proxyCache =…

Replies: 2 comments 2 replies

Comment options

You must be logged in to vote
1 reply
@PepijnSenders
Comment options

Answer selected by Ephem
Comment options

You must be logged in to vote
1 reply
@escaton
Comment options

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
4 participants