T
TanStack3y ago
dependent-tan

Download button: query or mutation?

Our application has several "Download" buttons for fetching various PDFs or CSVs from the server. Since these are idempotent GET requests, my inclination is to use useQuery — but because it can be a lot of data, we don't want to fetch immediately on page load. I haven't found any guidance either here, in the docs, in Github, or on Stack Overflow on which is the recommended mechanism: Option 1: useQuery A useQuery with enabled set to false, then using query.refetch() in our click handler. This used to be paired with an onSuccess callback to trigger a file save, but now has moved to a useEffect. This code looks approximately like:
export const useDownloadSampleCsv = () => {
const query = useQuery({
queryFn: ({ signal }) => getSampleCsv({ signal }),
queryKey: ["sample_csv"],
enabled: false,
});

useEffect(() => {
if (query.data) {
saveAs(query.data, "sample.csv");
}
}, [query.data]);

return query;
};

const DownloadButton = () => {
const download = useDownloadSampleCsv();
return <button onClick={download.refetch()}>Download</button>
}
export const useDownloadSampleCsv = () => {
const query = useQuery({
queryFn: ({ signal }) => getSampleCsv({ signal }),
queryKey: ["sample_csv"],
enabled: false,
});

useEffect(() => {
if (query.data) {
saveAs(query.data, "sample.csv");
}
}, [query.data]);

return query;
};

const DownloadButton = () => {
const download = useDownloadSampleCsv();
return <button onClick={download.refetch()}>Download</button>
}
Option 2: useMutation This seems more straightforward to implement, though is a bit incongruous with my mental model of when to use a mutation. Rough code:
export const useDownloadSampleCsv = () => {
return useMutation({
mutationFn: () => getSampleCsv(),
onSuccess: (data) => saveAs(data, "sample.csv")
});
};

const DownloadButton = () => {
const download = useDownloadSampleCsv();
return <button onClick={download.mutate()}>Download</button>
}
export const useDownloadSampleCsv = () => {
return useMutation({
mutationFn: () => getSampleCsv(),
onSuccess: (data) => saveAs(data, "sample.csv")
});
};

const DownloadButton = () => {
const download = useDownloadSampleCsv();
return <button onClick={download.mutate()}>Download</button>
}
14 Replies
xenophobic-harlequin
xenophobic-harlequin3y ago
You can also combine these approaches. useQuery to make good use of the cache and then a mutation that calls queryClient.ensureQueryData of that query. This has a nice onSuccess 🙂
dependent-tan
dependent-tanOP3y ago
Honestly that sounds more complicated. Wondering if @TkDodo 🔮 has a recommended best practice on this.
rare-sapphire
rare-sapphire3y ago
FYI, we use solution 1 on our side for the same pattern. In addition we had cacheTime: 0 so that we do not keep the file content in the cache. RQ is used "only" to have accurate loading/error state.
xenophobic-harlequin
xenophobic-harlequin3y ago
Then a mutation would be a better fit…
rare-sapphire
rare-sapphire3y ago
Hum, what bothers me is that "mutation" means "change" in my head, which is not the case here.
stormy-gold
stormy-gold3y ago
does the server create the PDF on the fly? If so I would say its a mutation
dependent-tan
dependent-tanOP3y ago
It depends on what’s being downloaded, but I think that’s a reasonable heuristic.
rare-sapphire
rare-sapphire3y ago
In our case nothing is generating. It is a file loading query to retrieve the content of the file in memory. Not « really » a mutation 😁 But it is triggered by a click on a button so not really a « query » neither
correct-apricot
correct-apricot3y ago
Is the native anchor download attribute an option?
dependent-tan
dependent-tanOP3y ago
Not really — we're fetching from an API that expects auth headers. We could feasibly work to change the API to accept some sort of token-based authentication param on the download endpoints. Based on this thread and the conversation with our team, I think we're likely to go with the simplest implementation which is useMutation. I agree with @glabat that "mutation" means "changes something on the server" in my head, but is maybe more realistically though of as "network interaction as the result of user action"
exotic-emerald
exotic-emerald3y ago
Do you know you can still use plain old fetch request to download a file? My point is simple if you don't need the data to be stored in the cache, don't use RQ for it. I also had a temptation to put everything in RQ since I used it, but after reasoning about it I came to the conclusion that's not the case. Downloading files is a utility functionality thus it should be on a low level, a simple request.
rare-sapphire
rare-sapphire3y ago
RQ helps with loading and error states
exotic-emerald
exotic-emerald3y ago
If that's all you need you can write it in a few lines of code. As for me, it's overhead to use RQ just for the sake of loading state and error handling
rare-sapphire
rare-sapphire3y ago
It depends as usual 😅. In our case it is more consistent with our code base. But I agree that it may add overhead in other cases.

Did you find this page helpful?