How to cancel requests with Apollo GraphQL
Mileta Dulovic
Let's say you have a big list of items to pull from backend on the /list
page. Well, you go to /list
page, request is triggered, and you wait for the data. While you wait for the data, you click a link to go to the next page, but what happens with request on /list
page? It is still active in the background. Why is this bad? Well, if you are expecting a big chunk of data, this can really slow down all the pages on the website.
useQuery
You would use useQuery
to fetch data from the server, something like this.
import { useQuery } from "@apollo/graphql";
const { data } = useQuery(GET_BIG_DATA, {
fetchPolicy: "network-only",
});
// do something with the data
How can we solve this
We can easily solve the issue by making a custom hook that we will use instead of default the useQuery
. Let's call it useAbortiveQuery
useAbortiveQuery
Hook looks something like this.
import { useState, useEffect } from "react";
import { useApolloClient } from "@apollo/client";
function useAbortiveQuery({ query, params, deps }) {
const [data, setData] = useState();
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
const apolloClient = useApolloClient();
useEffect(() => {
setLoading(true);
const watchedQuery = apolloClient.watchQuery({
query,
...params,
fetchPolicy: params?.fetchPolicy || "cache-and-network",
});
const sub = watchedQuery.subscribe({
next(x) {
if (!x.partial) {
setData(x.data);
setError(null);
setLoading(false);
}
},
error(err) {
setError(err);
setLoading(false);
},
complete() {
setLoading(false);
},
});
return () => {
sub.unsubscribe();
};
}, [deps]);
return { data, error, loading };
}
export { useAbortiveQuery };
As you can see, I am using watchQuery
instead of useQuery
as it gives me the ability to subscribe/unsubscribe to the query.
Now, every time user leaves page from where this hook is triggered, API call will be canceled, and no more wasted bandwidth, or slow pages changes.
I also exposed, loading
, and error
variables for easier UI manipulating.
Here is an example of using a hook
const { data, loading, error } = useAbortiveQuery({
query: GET_BIG_DATA,
params: {
fetchPolicy: "network-only",
variables: {
limit: 1000,
},
},
deps: id,
});
As you can see, syntax is really similar as in useQuery
with 1 aditional parameter, deps
. I added deps
as a way to refetch the query. Every time deps
change, useEffect
inside useAbortiveQuery
will trigger, and run the query again. Think of deps
as refetch
in useQuery
Conclusion
Feel free to copy the hook and tweak it to your own needs.
Also note that this hook is replacement for useQuery
, and not to useLazyQuery
. I don't feel the need to wrap useLazyQuery
as they should be triggered programatically, on actions, and in most cases should resolve quick, as they shouldn't ask for big chunks of data.
More in this category
Infinite scroll component in React and NextJS
Infinite scroll is a great way to load huge lists as it provides a great UX for user when compared to traditional pagination
Lazy loading images with Intersection Observer in React
Images can really be a bottleneck on the website. If you do not optimize your images users will have bad time and crawlers will not crawl you as fast as you want.
React: Pitch & anti-pitch
Weighing the Advantages and Disadvantages of React for Modern Web Development.