Kiran Mantha
Posted on May 22, 2023
As we all know apollo-graphwl client provide an awesome useLazyQuery
hook to make queries on demand. But the same is not true for react-query by tanstack. There's also few discussion threads in here and here
i created a custom useLazyQuery hook based on the ideas of above threads and this is the result:
// useLazyQuery.ts
import { GraphQLClient } from 'graphql-request';
import { QueryKey, UseQueryOptions, UseQueryResult, useQuery } from '@tanstack/react-query';
import { useState } from 'react';
interface QueryOptions<TInput, TQueryFnData, TResponse>
extends Omit<UseQueryOptions<TQueryFnData, unknown, TResponse, QueryKey>, 'onSuccess' | 'onError'> {
onSuccess?: (data: TResponse, input?: TInput) => void;
onError?: () => void;
}
const extractNameFromQuery = (query: string) => {
const keywordIndex =
query.indexOf(QueryType.QUERY) !== -1
? query.indexOf(QueryType.QUERY) + QueryType.QUERY.length
: query.indexOf(QueryType.MUTATION) + QueryType.MUTATION.length;
return query.substring(keywordIndex, query.indexOf('(')).replace(/ /g, '');
};
const getGraphQLClient = (
_queryName: string,
optionalHeader?: Record<string, string | number | boolean>
): GraphQLClient => {
return new GraphQLClient(env.REACT_APP_API_URL, {
headers: { } as Record<string, string>
});
};
const GQLInteraction = async <T,>(
schema: string,
variables?: Record<string, string[] | number | number[] | unknown> | undefined,
): Promise<T> => {
try {
const queryDescription = extractNameFromQuery(schema);
const client = getGraphQLClient(queryDescription, { ...optionalHeader });
return await client.request(schema, variables);
} catch (err) {
console.log('error', err);
throw err;
}
};
export function useLazyQuery<TInput extends Record<string, unknown>, TQueryFnData, TResponse = TQueryFnData>(
queryName: string,
query: string,
options: QueryOptions<TInput, TQueryFnData, TResponse> = {}
): [(input: TInput) => void, UseQueryResult<TResponse, unknown>] {
const [variables, setVariables] = useState<TInput>();
const queryOptions = {
refetchOnWindowFocus: false,
retry: 0,
enabled: Boolean(variables),
select: data => {
let returnValue: unknown = data;
if (options.select) {
returnValue = options.select(data);
}
return returnValue as never as TResponse;
},
onSuccess: (data: TResponse) => {
options.onSuccess?.(data, variables);
setVariables(undefined);
},
onError: () => {
options.onError?.();
setVariables(undefined);
}
};
const queryInfo = useQuery<TQueryFnData, unknown, TResponse, QueryKey>(
[queryName, variables],
() => GQLInteraction(query, variables),
queryOptions
);
return [setVariables, queryInfo];
}
And this is how i'm using it:
// queries.ts
const getPersonDetail = `query person(id: $id) {
getPersonDetail(id: $id) {
id
name
}
}`;
interface Person {
id: number;
name: string
}
interface PersonResponse {
getPersonDetails: Person;
}
export const useGetPersonDetails = (onSuccess: (person: Person) => void, onError?: () => void) => {
const options = {
onSuccess: (data: PersonResponse, input?: { personId: number }) => {
onSuccess(data.getPersonDetails);
},
onError: () => {
onError?.();
},
};
return useLazyQuery<{ personId: number }, PersonResponse>(
"getPersonDetail",
getPersonDetail,
options
);
};
sometimes we may need to access api payload in success method. in that case, in options.onSuccess
that details can be accessed by the optional 2nd parameter input
as shown above.
That's it for the day.
Don't forget to share your implementation in comments below 👇
Thanks,
Kiran 👋
💖 💪 🙅 🚩
Kiran Mantha
Posted on May 22, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.