Last active
August 22, 2024 01:06
-
-
Save Jonir227/03fee841ecb9e9bd3d23865261254edd to your computer and use it in GitHub Desktop.
react-query utils
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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