Skip to content

Instantly share code, notes, and snippets.

@fmartins-andre
Created April 17, 2025 20:35
Show Gist options
  • Save fmartins-andre/a7117c56da1eb631d48ad470ac0a131e to your computer and use it in GitHub Desktop.
Save fmartins-andre/a7117c56da1eb631d48ad470ac0a131e to your computer and use it in GitHub Desktop.
Factory function to create tanstack react lazy query hooks
/**
* execute a callback and return an result/error array
*
* @param {callback} function to be executed
*/
export const safePromise = async <TData, TError>(
callback: () => Promise<TData>
): Promise<[false, TError, undefined] | [true, undefined, TData]> => {
try {
const response: TData = await callback()
return [true, undefined, response]
} catch (error) {
return [false, error as TError, undefined]
}
}
import {
DefaultError,
UseBaseQueryOptions,
useQueryClient,
} from '@tanstack/react-query'
import { useCallback, useState } from 'react'
import { safePromise } from './safe-promise'
type LazyQueryState<TParams> = {
isFetching: boolean
params: TParams | undefined
}
export type LazyQueryOptions<TParams, TData, TError = unknown> = {
onQuery?: (params: TParams, cachedData: TData | undefined) => void
onSuccess?: (params: TParams, data: TData) => void
onError?: (params: TParams, error: TError) => void
onSettled?: (
params: TParams,
error: TError | undefined,
data: TData | undefined
) => void
}
export type LazyQueryHook<TParams, TData, TError = unknown> = (
options?: LazyQueryOptions<TParams, TData, TError>
) => [
/**
* Trigger function: invokes the query with params and returns the result
*/
(params: TParams) => Promise<TData>,
/**
* Query state: includes data, status flags, error, etc.
*/
{
data: TData | undefined
isFetching: boolean
isLoading: boolean
},
]
/**
* createLazyQueryHook
* @param clientOptions - A function that returns query options for the given parameters
*/
export function createLazyQueryHook<TParams, TData, TError = DefaultError>(
clientOptions: (
params: TParams
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => UseBaseQueryOptions<any, TError, TData, any, any>
): LazyQueryHook<TParams, TData, TError> {
try {
return (options?: LazyQueryOptions<TParams, TData, TError>) => {
const queryClient = useQueryClient()
const [state, setState] = useState<LazyQueryState<TParams>>({
isFetching: false,
params: undefined,
})
const queryOpts = clientOptions(
state.params ?? ({} as unknown as TParams)
)
const data = queryClient.getQueryData<TData>(queryOpts.queryKey)
const trigger = useCallback(
async (params: TParams) => {
try {
setState({ isFetching: true, params })
options?.onQuery?.(params, data)
const queryOpts = clientOptions(params)
const [ok, error, result] = await safePromise<TData, TError>(
() => queryClient.fetchQuery(queryOpts) as Promise<TData>
)
setState((prev) => ({ ...prev, isFetching: false }))
options?.onSettled?.(params, error, result)
if (!ok) {
options?.onError?.(params, error)
throw error
}
options?.onSuccess?.(params, result)
return result
} catch (error) {
console.error(
'Error in lazy query trigger function. Please check the parameters and types.',
error
)
throw error
}
},
[queryClient, options, data]
)
return [
trigger,
{
data,
isFetching: state.isFetching,
isLoading: state.isFetching && !data,
},
]
}
} catch (error) {
console.error(
'Error creating lazy query hook. Please check the parameters and types.',
error
)
throw error
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment