Last active
November 3, 2022 15:18
-
-
Save omerman/d650c0b9c654c2540dbdcd81349892a6 to your computer and use it in GitHub Desktop.
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 { QueryFunctionContext } from "@tanstack/react-query"; | |
import { Updater } from "@tanstack/query-core/build/types/packages/query-core/src/utils"; | |
import { axiosClient } from "../axios/axiosClient"; | |
import { queryClient } from "./queryClient"; | |
export class ApiQuery<Data extends object & { __OBJECT?: any }, Arg = void> { | |
static skipToken = "$_$_SKIP_$_$" as const; | |
readonly buildApiPathKey: (arg: Arg) => string; | |
constructor( | |
readonly apiPath: void extends Arg ? string : (arg: Arg) => string, | |
readonly options?: { | |
customFetcher?: (url: string) => Promise<Data>; | |
} | |
) { | |
if (typeof apiPath === "function") { | |
this.buildApiPathKey = apiPath as any; | |
} else { | |
this.buildApiPathKey = () => apiPath as any; | |
} | |
} | |
// Use this in pages folder network requests. | |
invokeServerSideRequest = (...args: void extends Arg ? [] : [Arg]) => { | |
const apiPath = this.buildApiPathKey(args[0]!); | |
return axiosClient.get<Data>(apiPath, { | |
baseURL: process.env.SERVER_SIDE_BASE_URL, | |
}); | |
}; | |
updateCache( | |
...args: void extends Arg | |
? [Updater<Data, Data>] | |
: [Arg, Updater<Data, Data>] | |
) { | |
const key = | |
args.length === 1 | |
? this.buildApiPathKey(undefined as any) | |
: this.buildApiPathKey(args[0]); | |
const updater = args.length === 1 ? args[0] : args[1]; | |
if (typeof updater === "function") { | |
if (queryClient.getQueryData([key]) === undefined) return; | |
queryClient.setQueryData<Data>([key], (cachedValue) => { | |
return updater(cachedValue!); | |
}); | |
} else { | |
queryClient.setQueryData([key], updater); | |
} | |
} | |
prefetch(...args: void extends Arg ? [] : [Arg]) { | |
const key = | |
args.length === 1 | |
? this.buildApiPathKey(args[0]) | |
: this.buildApiPathKey(undefined as any); | |
queryClient.prefetchQuery([key], (...args) => this.fetch(...args)); | |
} | |
// Use this in pages folder network requests. | |
invalidate = (...args: void extends Arg ? [] : [Arg]) => { | |
const apiPath = this.buildApiPathKey(args[0]!); | |
return queryClient.invalidateQueries([apiPath]); | |
}; | |
// Use this when query data doesn't consist of one type. | |
dataCast<CastData extends object>(): ApiQuery<CastData, Arg> { | |
return this as any; | |
} | |
async fetch({ queryKey }: QueryFunctionContext) { | |
const url = queryKey[0] as string; | |
const defaultFetcher = async () => { | |
const response = await axiosClient.get<Data>(url); | |
return response.data; | |
}; | |
const { customFetcher } = this.options ?? {}; | |
return customFetcher?.(url) ?? defaultFetcher(); | |
} | |
} | |
export type SkipTokenType = typeof ApiQuery.skipToken; |
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 { ApiQuery } from "@project/core/react-query/ApiQuery"; | |
import { SongListItem } from "./types"; | |
export class SongsApi { | |
static listQuery = new ApiQuery<SongListItem[]>("/api/songs"); | |
} |
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 { AxiosError } from "axios"; | |
import { | |
UseQueryOptions, | |
UseQueryResult, | |
useQuery, | |
} from "@tanstack/react-query"; | |
import { ApiQuery, SkipTokenType } from "./ApiQuery"; | |
type ArgParamater<Arg> = | |
| Arg | |
| { [key in keyof Arg]: Arg[key] | SkipTokenType } | |
| SkipTokenType; | |
export interface UseApiQuery { | |
<Data extends object, Arg>( | |
apiQuery: ApiQuery<Data, Arg>, | |
...args: void extends Arg | |
? [UseQueryOptions<Data, AxiosError>?] | |
: [ArgParamater<Arg>, UseQueryOptions<Data, AxiosError>?] | |
): UseQueryResult<Data, AxiosError>; | |
} | |
export const useApiQuery: UseApiQuery = (apiQuery, ...args) => { | |
const arg = | |
args[0] === ApiQuery.skipToken | |
? ApiQuery.skipToken | |
: parseArg(args[0] as any); | |
const overrideOptions = (args.length === 1 ? args[0] : args[1]) as | |
| UseQueryOptions<any, any> | |
| undefined; | |
const queryKey = | |
arg === ApiQuery.skipToken | |
? [ApiQuery.skipToken] | |
: [apiQuery.buildApiPathKey(arg!)]; | |
const { enabled = true } = overrideOptions ?? {}; | |
return useQuery<any, any>({ | |
...overrideOptions, | |
queryKey, | |
queryFn: (...args) => apiQuery.fetch(...args), | |
enabled: enabled && arg !== ApiQuery.skipToken, | |
}); | |
}; | |
/* | |
1. Will convert arg of object shape, for example {x: 1, y: <skipToken>}, to skipToken. | |
2. Will convert arg of array shape, for example [x, <skipToken>], to skipToken. | |
3. For All other shapes/values, parseArg will return the given arg AS IS. | |
*/ | |
const parseArg = <Arg>( | |
arg: Arg | SkipTokenType | { [key in keyof Arg]: Arg[key] | SkipTokenType } | |
): Arg | SkipTokenType => { | |
if (arg === ApiQuery.skipToken) return ApiQuery.skipToken; | |
if (typeof arg !== "object") return arg; | |
if (Object.values(arg).some((x) => x === ApiQuery.skipToken)) | |
return ApiQuery.skipToken; | |
return arg as Arg; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment