Skip to content

Instantly share code, notes, and snippets.

@Jonir227
Last active August 22, 2024 01:06
Show Gist options
  • Save Jonir227/03fee841ecb9e9bd3d23865261254edd to your computer and use it in GitHub Desktop.
Save Jonir227/03fee841ecb9e9bd3d23865261254edd to your computer and use it in GitHub Desktop.
react-query utils
import {dehydrate, QueryClient} from '@tanstack/react-query'
import type {QueryFunctionContext} from '@tanstack/react-query'
import type {AxiosInstance, AxiosRequestConfig} from 'axios'
export function createQueryFetcher(axiosInstance: AxiosInstance) {
return async function fetcher<Data>(config: AxiosRequestConfig) {
const {data} = await axiosInstance.request<Data>(config)
return data
}
}
type TScope = {key: string; meta?: string | number | Record<string, string | number>}
type TReactQueryFetcher<TData> = (params: AxiosRequestConfig) => Promise<TData>
type TReactQueryFetcherResult<TData> = ReturnType<TReactQueryFetcher<TData>>
type TConfigAsKey = Pick<AxiosRequestConfig, 'url' | 'baseURL' | 'data' | 'headers' | 'method' | 'params'>
type TQueryKey = readonly [TScope, TConfigAsKey] | readonly [TConfigAsKey]
type TReactQueryParams<TData> = {
queryKey: TQueryKey
queryFn: (params: QueryFunctionContext<TQueryKey>) => TReactQueryFetcherResult<TData>
}
type TServerQuery<TData> = (fetcher: TReactQueryFetcher<unknown>) => TReactQueryParams<TData>
type TQueryOption = {
config?: AxiosRequestConfig
}
type TBaseCreatedQuery = {
scope: (option?: {withMeta: boolean}) => TScope
}
type TNoPramCreatedQuery<TData> = TBaseCreatedQuery & {
key: () => TQueryKey
query: (option?: TQueryOption) => TReactQueryParams<TData>
serverQuery: (option?: TQueryOption) => TServerQuery<TData>
mutation: () => {mutationFn: (config?: AxiosRequestConfig) => TReactQueryFetcherResult<TData>}
}
type TWithPramCreatedQuery<TData, TParam> = TBaseCreatedQuery & {
key: (param: TParam) => TQueryKey
query: (params: TParam, option?: TQueryOption) => TReactQueryParams<TData>
serverQuery: (params: TParam, option?: TQueryOption) => TServerQuery<TData>
mutation: () => {mutationFn: (params: TParam, config?: AxiosRequestConfig) => TReactQueryFetcherResult<TData>}
}
const isQueryOption = (option: unknown): option is TQueryOption =>
!!option && typeof option === 'object' && 'config' in option
const getQueryArguments = (paramOrOption: any = {}, _option?: any) => {
const param = isQueryOption(paramOrOption) ? undefined : paramOrOption
const option = isQueryOption(paramOrOption) ? paramOrOption : _option
return {
param,
option,
}
}
export const createQueryFactory = (axiosInstance: AxiosInstance) => {
const clientFetcher = createQueryFetcher(axiosInstance)
function createQuery<TData>(
getConfig: () => TConfigAsKey,
options?: {
scope?: TScope
},
): TNoPramCreatedQuery<TData>
function createQuery<TData, TParam>(
getConfig: (params: TParam) => TConfigAsKey,
options?: {
scope?: TScope
},
): TWithPramCreatedQuery<TData, TParam>
function createQuery(
getConfig: (params?: any) => TConfigAsKey,
{
scope,
}: {
scope?: TScope
} = {},
) {
const getScope = ({withMeta}: {withMeta: boolean} = {withMeta: true}) => {
if (!scope) {
throw new Error('[createQuery] scope of query is not defined')
}
return {
key: scope?.key,
...(withMeta && scope?.meta ? {meta: scope.meta} : {}),
}
}
const key = (param?: any) => {
const config = getConfig(param)
return scope ? ([scope, config] as const) : ([config] as const)
}
const makeQueryOption = (fetcher: TReactQueryFetcher<any>, paramOrOption: any = {}, option?: any) => {
const {param, option: _option} = getQueryArguments(paramOrOption, option)
return {
queryKey: key(param),
queryFn: ({queryKey}: QueryFunctionContext<TQueryKey>) => {
const config = queryKey[queryKey.length - 1]
return fetcher({...config, ..._option?.config})
},
}
}
const query = (paramOrOption: any = {}, option?: any) => makeQueryOption(clientFetcher, paramOrOption, option)
const serverQuery =
(paramOrOption: any = {}, option?: any) =>
(serverFetcher: TReactQueryFetcher<any>) =>
makeQueryOption(serverFetcher, paramOrOption, option)
const mutation = () => {
return {
mutationFn: (paramOrOption?: any, option?: any) => {
const {param, option: _option} = getQueryArguments(paramOrOption, option)
return clientFetcher({...getConfig(param), ..._option?.config})
},
}
}
return {
scope: getScope,
key,
query,
serverQuery,
mutation,
}
}
return createQuery
}
/**
* dehydratedState 값에 undefined 이 존재한다면, SerializableError 로 서버 에러가 발생한다.
* 때문에 아래와 같이 JSON.stringify -> JSON.parse 로 처리해 undefined 필드를 제거해준다.
*/
export const getDehydratedState = (queryClient: QueryClient) => JSON.parse(JSON.stringify(dehydrate(queryClient)))
export async function prefetchReactQueries(
query: TServerQuery<unknown> | TServerQuery<unknown>[],
axiosInstance: AxiosInstance,
queryClient: QueryClient = new QueryClient(),
) {
const serverFetcher = createQueryFetcher(axiosInstance)
const queries = Array.isArray(query) ? query : [query]
const tasks = queries.map((q) => queryClient.prefetchQuery(q(serverFetcher)))
await Promise.allSettled(tasks)
return {queryClient}
}
export function createScope<S>(scope: S): {base: {key: S}}
export function createScope<S, K extends Record<string, unknown>>(
scope: S,
meta: K,
): {[Key in keyof K]: {key: S; meta: K[Key]}} & {base: {key: S}}
export function createScope(key: string, meta?: Record<string, unknown>) {
return Object.entries(meta ?? {}).reduce((acc, [k, v]) => ({...acc, [k]: {key, meta: v}}), {
base: {key},
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment