useLazyQuery for @tanstack/react-query
Tags: react, graphql, tanstack
Published: 2023-05-22 07:20:31

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](https://github.com/TanStack/query/discussions/1205) and [here](https://github.com/TanStack/query/discussions/1549)

i created a custom useLazyQuery hook based on the ideas of above threads and this is the result:

```typescript
// 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:

```typescript
// 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 :point_down:

Thanks,
Kiran :wave: