DEV Community

Kiran Mantha
Kiran Mantha

Posted on • Edited on

useLazyQuery for @tanstack/react-query

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];
}
Enter fullscreen mode Exit fullscreen mode

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
    );
};
Enter fullscreen mode Exit fullscreen mode

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 👋

Top comments (0)