Avraj
Avraj7mo ago

Is there a way to refetch a query with new parameters?

Hi I'm using tRPC in a Next.js app and I have a button that a user can click to get the latest data from the server. By default this data is cached in the server because the query is expensive. I've added an optional param/input in my query that looks like this:
guilds: privateProcedure
.input(
z
.object({
skipCache: z.boolean().optional(),
})
.optional()
)
.query(async ({ input, ctx }) => {
// Get data
});
guilds: privateProcedure
.input(
z
.object({
skipCache: z.boolean().optional(),
})
.optional()
)
.query(async ({ input, ctx }) => {
// Get data
});
So when { skipCache: true } is set, the user can bypass the cache and get the latest data. I'm feeling a bit lost however when it comes to the implementation itself. I can't find anything in the docs regarding refetching with new/updated params.
const trpcUtils = trpc.useUtils(); // This will return cached data from the server.

const { data: guilds, error } = trpc.private.guilds.useQuery();

const handleRefresh = () => {
// The implementation to bypass cached data => { skipCache: true }
}

return <button onClick={handleRefresh}>Refresh</button>
const trpcUtils = trpc.useUtils(); // This will return cached data from the server.

const { data: guilds, error } = trpc.private.guilds.useQuery();

const handleRefresh = () => {
// The implementation to bypass cached data => { skipCache: true }
}

return <button onClick={handleRefresh}>Refresh</button>
Is this something that's possible?
Solution:
What you can do is just to have a skipCache state and pass this to your useQuery. ```tsx const [skipCache, setSkipCache] = useState(false); ...
Jump to solution
18 Replies
BeBoRE
BeBoRE7mo ago
If you invalidate the query it will refetch https://trpc.io/docs/client/react/useUtils#query-invalidation
useUtils | tRPC
useUtils is a hook that gives you access to helpers that let you manage the cached data of the queries you execute via @trpc/react-query. These helpers are actually thin wrappers around @tanstack/react-query's queryClient methods. If you want more in-depth information about options and usage patterns for useContext helpers than what we provide h...
BeBoRE
BeBoRE7mo ago
so
const utils = trpc.useUtils()

utils.private.guilds.invalidate()
const utils = trpc.useUtils()

utils.private.guilds.invalidate()
BeBoRE
BeBoRE7mo ago
Also you shouldn't skip the cache, it's better to set the time till the data is stale. If the data is stale useQuery will automatically refetch when it's run. You can also set the refetchInterval, so that the data gets refetched every n milliseconds given https://tanstack.com/query/latest/docs/react/reference/useQuery
Avraj
Avraj7mo ago
thanks i've tried this before, but such is the feature. if a user wants the latest data it makes for bad ux to make them wait a certain time. also as mentioned it's an expensive query so it should only be done every now and then, which is why the cache i have on the server lasts 15 mins. is there no workaround at all for this?
BeBoRE
BeBoRE7mo ago
Ah sorry, had misread your question. I still don't fully understand however. What's the problem with the invalidate method?
Avraj
Avraj7mo ago
so i've used invalidation before, but the reason it doesn't work in my case is that it uses the same query details to make another request to the server. there's no way for me to explicitly state in the invalidation that this specific request is supposed to have a new set of parameters. i was thinking of maybe creating a new route in my server to invalidate the cache and return new data but that leads to 2 routes doing almost the same thing. they'll both return the same data, but one does with cache and one without
BeBoRE
BeBoRE7mo ago
invalidate() without input invalidates all queries, regardless of input. So invalidate() will invalidate even a query like trpc.private.guilds() and trpc.private.guilds({skipCache: true}). If you only want to invalidate the query where you used a specific input like trpc.private.guilds({skipCache: true}) then you can call utils.private.guilds.invalidate({skipCache: true}) Or do you mean that you first want to do it without cache, and then with cache?
Avraj
Avraj7mo ago
the other way around actually when the page is first loaded, the data that is fetched should be from the cache (if it's available in the server). but the user will now have the chance to fetch the latest data by clicking a button. this button is supposed to invalidate the cache in the server and return the most up to date data imagine a discord bot dashboard where you have a list of servers once a user is in this page, say they create a new server in discord but now that server is not part of the list in the web app because it's still using the cache which will last another 15 mins the refresh button will give them the option to fetch the latest array of servers
Solution
BeBoRE
BeBoRE7mo ago
What you can do is just to have a skipCache state and pass this to your useQuery.
const [skipCache, setSkipCache] = useState(false);

const {data: guilds} = trpc.private.guilds.useQuery({skipCache});

const utils = trpc.useUtils();

const handleRefresh = () => {
setSkipCache(true);
utils.private.guilds.invalidate();
}
const [skipCache, setSkipCache] = useState(false);

const {data: guilds} = trpc.private.guilds.useQuery({skipCache});

const utils = trpc.useUtils();

const handleRefresh = () => {
setSkipCache(true);
utils.private.guilds.invalidate();
}
Avraj
Avraj7mo ago
alright so i've tried this approach too and there is one slight issue. let me explain. everything works well until the handleRefresh function has stopped calling. say the user wants to refresh the list of guilds once again. the skipCache state is now stuck at true and handleRefresh won't do anything anymore when called changing it back to false at the end of the handleRefresh function doesn't seem to do anything either because of how react handles state changes
BeBoRE
BeBoRE7mo ago
Why would handleRefresh not do anything after the first refetch? invalidate() will always force a refetch.
Avraj
Avraj7mo ago
you're definitely right there, but there is still an issue of requests sent on behalf of tRPC when focusing on the window. i like that feature but when focusing back on the window it'll send new requests. but now it'll send with skipCache set to true because the state still hasn't changed back to false
BeBoRE
BeBoRE7mo ago
And you want to keep refetchOnWindowFocus enabled?
Avraj
Avraj7mo ago
it would be nice to have that, but if i can keep that turned off i believe that fixes the issue right? tRPC doesn't send any other requests automatically i'm guessing?
BeBoRE
BeBoRE7mo ago
Yes, setting that to false disables that behavior. If you do want to keep it enabled, you might just need to use the queryClient manually and then update the queryKey yourself manually like you would when doing an optimistic update. If you understand what I mean.
Avraj
Avraj7mo ago
i think for now i'll disable refetchOnWindowFocus. in case i find a reason for it to be enabled i'll have to do a little more research on using the queryClient xD i believe that solves the issue for me
humblemodulo
humblemodulo2mo ago
I'm sure it's a case of me trying to use it incorrectly, but . . . Depending on state to trigger a refetch seems "wrong". 🤔 Is there no "freedom" to manually refetch with different/additional params as allowed by the actual endpoint? Is there a reason we're "handcuffed" in this way? Admittedly, the primary reason I'm using TRPC in the first place is for "ease of validation" and a bit of typesafety, but if I'm forced to rely on state for various individual params of a potentially larger params object, it might be "better" to just use an old school RESTful setup. 😢
BeBoRE
BeBoRE2mo ago
It’s just not a use-case tRPC solves, you can use the trpcClient and React Query manually if you want more freedom. Or just use the client standalone.