Repository navigation
Updating React Query Cache with Presigned URLs #10014
Replies: 2 comments
|
The tkdodo rule is about not using the cache as Redux for client-invented state. Your case isn't that. The presigned URL did originate server-side (S3 SDK generated it on upload), you're just stitching it back together because your The cleanest fix is to make const { mutateAsync: registerImagePath } = useMutation(
trpc.image.registerPath.mutationOptions({
onSuccess: (newEntry) => {
queryClient.setQueryData(
trpc.image.getById.queryKey({ id }),
(prev) => prev ? [...prev, newEntry] : [newEntry],
)
},
}),
)If you can't change the server contract, what you have is fine — it's not violating the rule, the data is server-sourced. But you have a real problem with presigned URLs that the rule doesn't cover: expiration. If your URL TTL is shorter than how long a user might keep the page open, the cached URL goes stale and images break with no refetch triggered. Two ways to handle that:
For most apps option 1 is enough. |
|
The issue with the current approach isn't The idiomatic split is: the cache holds durable data, and the presigned URL is treated as derived data with its own lifetime.
const usePresignedUrl = (path: string) =>
useQuery({
queryKey: ['presignedUrl', path],
queryFn: () => generatePresignedUrl(path), // your signing call
staleTime: 1000 * 60 * 50, // safely under the URL's actual TTL
});Each rendered image resolves its URL through this query. React Query then refetches the URL before it expires, dedupes signing calls across components, and the URL lifetime is managed by
queryClient.setQueryData(['presignedUrl', newPath], freshUrl);That gives you the instant preview while keeping the two lifetimes separate. And if you'd rather not hand-maintain the list at all, invalidating In short: cache the path, query the URL. The expiry belongs to the URL, so the URL gets the query key. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
setQueryData is not directly writing the response of registerImagePath or deleteImagePath
Instead, it writes a locally constructed object (newEntry) that includes a newly generated presigned URL
Questions
Does this approach violate the recommended usage of setQueryData?
Even though the presigned URL is generated by the server, it is not returned as part of the mutation response.
Is there a more idiomatic or recommended approach for handling presigned URLs with expiration, while avoiding unnecessary invalidation and regeneration?
https://tkdodo.eu/blog/practical-react-query#dont-use-the-querycache-as-a-local-state-manager
“Don’t use the query cache as a local state manager.
Use it only for optimistic updates or for writing data received from the backend.”
All reactions