Customize GraphQL Errors with React useQuery Hook
In this lesson, we'll address how we can handle the loading and error state of our GraphQL queries with our custom useQuery Hook.
Custom useQuery and loading/error states#
📝 This lesson's quiz can be found - here.
🗒️ Solutions for this lesson's quiz can be found - here.
Though our useQuery
Hook works as intended, we haven't taken into account the tracking of the loading and error states of our queries.
Loading#
We'll first address the loading state of our request. When we say loading, we're essentially referring to being able to track the status of our asynchronous request. If the request is in flight, the UI should reflect this with a loading indicator of sorts. And when complete, we should be presented with the expected data.
To keep track of loading, we'll introduce a new loading field into the State
interface of the state object tracked in our custom useQuery
Hook. We'll declare the type of the loading
field as boolean
.
interface State<TData> {
data: TData | null;
loading: boolean;
}
We'll initialize the loading
value as false
in our state initialization.
export const useQuery = <TData = any>(query: string) => {
const [state, setState] = useState<State<TData>>({
data: null,
loading: false
});
// ...
};
At the beginning of the fetchApi()
method within the memoized fetch
callback, we'll set the loading
state to true
while also specifying that our state data
is still null
. When a request is complete we'll set loading
back to false
.
export const useQuery = <TData = any>(query: string) => {
const [state, setState] = useState<State<TData>>({
data: null,
loading: false
});
const fetch = useCallback(() => {
const fetchApi = async () => {
setState({ data: null, loading: true });
const { data } = await server.fetch<TData>({
query
});
setState({ data, loading: false });
};
fetchApi();
}, [query]);
// ...
};
We've already used the spread syntax to return everything within state
at the end of useQuery
. We can now destruct the loading
property from the useQuery
Hook used in the <Listings>
component.
If loading
is ever true
in our <Listings>
component, we'll render a simple header tag that says 'Loading...'
. When loading
is set back to false, the title and the listings list is to be shown.
export const Listings = ({ title }: Props) => {
const { data, loading, refetch } = useQuery<ListingsData>(LISTINGS);
// ...
if (loading) {
return <h2>Loading</h2>;
}
return (
<div>
<h2>{title}</h2>
{listingsList}
</div>
);
};
We'll ensure both the Node server and React client apps are running.
server $: npm run start
client $: npm run start
And in the browser, we'll now notice a brief 'Loading...'
message when the query request is in flight.

Errors#
We'll now address what would happen if our server.fetch()
function was to error out since our <Listings>
component isn't currently prepared to handle this.
With Apollo Server and GraphQL, errors can be a little unique. Oftentimes when a query has failed and returns an error - our server may treat that query as successful since the query request was made successfully.
Let's see an example of this. We'll briefly dive back into out Node server application and take a look at the listings
resolver function within the server/src/graphql/resolvers/Listing/index.ts
file.
export const listingResolvers: IResolvers = {
Query: {
listings: async (
_root: undefined,
_args: {},
{ db }: { db: Database }
): Promise<Listing[]> => {
return await db.listings.find({}).toArray();
}
}
// ...
};
The listings
resolver simply returns all the listing documents from the database collection we've set up in MongoDB Atlas. We'll temporarily throw an error before the return
statement of the resolver function to mimic if an error was to occur.
export const listingResolvers: IResolvers = {
Query: {
listings: async (
_root: undefined,
_args: {},
{ db }: { db: Database }
): Promise<Listing[]> => {
throw new Error("Error!");
return await db.listings.find({}).toArray();
}
}
// ...
};
We can refresh our browser to attempt to query the listings
field again. We're not going to get the information we're expected from the API but if we take a look at our browser's Network
tab and find the post API request made, we can see that the request made to /api
was successful with status code 200!

If we take a look at the response from the API request, whether through the browser Network
tab or GraphQL Playground, we can see the server returns data
as null and an errors
array is populated.
This page is a preview of TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL