T
TanStack9mo ago
passive-yellow

Merge useQuery results

The api I work with often has the ability to fetch "moreData". For example:
const useTodos = ({ id, moreData }) =>
useQuery({ queryKey: ['todos', id], queryFn: fetchTodos({ id, moreData }) });
const useTodos = ({ id, moreData }) =>
useQuery({ queryKey: ['todos', id], queryFn: fetchTodos({ id, moreData }) });
If moreData is undefined then just the base result is returned. For example maybe something like post, author , etc. If you supply moreData: ['image', 'comments'] then the result will return an object with those keys as well. What I'd like to do is keep these queries within the same cache regardless of whether or not moreData is supplied. The example above would do that - but the problem with keeping moreData out of the queryKey is that there will be times in the app where, for example, comments will be undefined when it shouldn't be. Imagine initially being on the Comments page, then going to just the Post page, then back to the Comments page. By the time you get back to the Comments page - comments will initially be undefined until the fetch successfully returns and updates the cache again. Anyone else encounter a similar problem to this? Now that I'm thinking about it I wonder if keepPreviousData might be useful here...
7 Replies
continuing-cyan
continuing-cyan9mo ago
Is there a reason why you want to store your cache in the same place despite having different dependencies(moreData)?
passive-yellow
passive-yellowOP9mo ago
Perhaps storing in the same cache isn't the right answer... This is sort of a similar problem to what keepPreviousData solves but this case wouldn't be solved by that option
continuing-cyan
continuing-cyan9mo ago
If you don’t mind, could you send me a CodeSandbox?
passive-yellow
passive-yellowOP9mo ago
sure thing - https://codesandbox.io/p/sandbox/x9d42w mocking routing here with just a couple buttons and useState I've given an example of both approaches - keeping the moreData out of the query key vs having moreData be part of the queryKey It really feels like this would be a situation for onSuccess but that of course is no longer an option for useQuery Keeping them as separate caches seems like the better approach (and this currently how I am solving this problem in production) but as you can see, when you switch from the detailed view to the basic view, the different-cache-basic-post initially has no data even though really, we do know the data from the detailed call
continuing-cyan
continuing-cyan9mo ago
First of all, thank you for providing the code sandbox. 🙂
What I'd like to do is keep these queries within the same cache regardless of whether or not moreData is supplied.
It may be more effective to separate this query into a different cache. Think of it as one repository for each API endpoint. and you don't have to distribute and manage data taken from one repository. if you want to reduce UI flickering, how about avoiding component unmounting and managing it conditionally instead? (below sandbox link) I’m not sure if my limited skills were able to fully assist you, but I hope it was somewhat helpful. 🥹 sandbox here: https://codesandbox.io/p/sandbox/usequery-merge-problem-forked-wwl2j7?file=%2Fsrc%2FPostDetailedDifferentCache.jsx%3A24%2C17
stormy-gold
stormy-gold9mo ago
what if you do this
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { mockPost, mockMoreData } from "./mockData";
/**
* @param {object} param0
* @param {number} param0.postId
* @param {string[]} param0.moreData
*/
export function usePostDifferentCache({ postId, moreData }) {
const queryClient = useQueryClient();
return useQuery({
queryKey: ["post", { postId, moreData }],
queryFn: () => {
return new Promise((resolve) => {
const response = moreData
? // mock the more data option
{ ...mockPost, comments: mockMoreData.comments }
: // otherwise return the basic data
mockPost;

console.log({ response, moreData });
setTimeout(() => {
resolve(response);
}, 1000);
});
},
placeholderData: () =>
queryClient.getQueriesData({
queryKey: ["post", { postId }],
})[0][1],
});
}
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { mockPost, mockMoreData } from "./mockData";
/**
* @param {object} param0
* @param {number} param0.postId
* @param {string[]} param0.moreData
*/
export function usePostDifferentCache({ postId, moreData }) {
const queryClient = useQueryClient();
return useQuery({
queryKey: ["post", { postId, moreData }],
queryFn: () => {
return new Promise((resolve) => {
const response = moreData
? // mock the more data option
{ ...mockPost, comments: mockMoreData.comments }
: // otherwise return the basic data
mockPost;

console.log({ response, moreData });
setTimeout(() => {
resolve(response);
}, 1000);
});
},
placeholderData: () =>
queryClient.getQueriesData({
queryKey: ["post", { postId }],
})[0][1],
});
}
passive-yellow
passive-yellowOP9mo ago
@janglad - you beat me to it. That's exactly right. I should have searched the docs harder - the example is right there: https://tanstack.com/query/latest/docs/framework/react/guides/placeholder-query-data
Placeholder Query Data | TanStack Query React Docs
What is placeholder data? Placeholder data allows a query to behave as if it already has data, similar to the initialData option, but the data is not persisted to the cache. This comes in handy for si...

Did you find this page helpful?