The function axiosGet
:
export const axiosGet = async <TData>({
url,
params = {},
onDone,
onError
}: TypeAxiosArgs<TData>) => {
try {
const r = await axios.get<TypeAxiosData<TData, typeof params.paginate>>(url, { params })
let ret
if (params?.paginate) {
ret = r.data
} else {
ret = r.data.data // <--- causing typescript to scream, check below
}
onDone?.(ret)
return ret
} catch (e) {
console.error(e)
onError?.(e)
}
}
The types:
export type TypeAxiosArgs<TData> = {
url: string
params?: { paginate?: number } & Record<string, any>
onDone?: (response: TypeAxiosData<TData, undefined>) => void
onError?: (error: any) => void
}
export type TypeAxiosData<
TData,
TPaginate extends number | undefined
> = TPaginate extends undefined
? TData
: {
data: TData
meta: {
from: number
to: number
total: number
last_page: number
}
}
I'm getting an error on r.data.data
if params.paginate
is undefined
Property 'data' does not exist on type 'TypeAxiosData<TData, number | undefined>'.
Property 'data' does not exist on type 'TData'.ts(2339)
I feel like the problem is with the axios.get
generic which needs to be something different but I can't figure out what.
export const axiosGet = async <TData>({
url,
params = {},
onDone,
onError
}: TypeAxiosArgs<TData>) => {
try {
const r = await axios.get<TypeAxiosData<TData, typeof params.paginate>>(url, { params })
let ret
if (params?.paginate) {
ret = r.data as TypeAxiosData<TData, number>
} else {
ret = r.data.data as TypeAxiosData<TData, undefined>
}
onDone?.(ret)
return ret
} catch (e) {
console.error(e)
onError?.(e)
}
}
axiosGet<{ id: number, name: string }>({ url: "", params: { paginate: undefined } })
.then((result) => {})
The problem I'm having is that the type of result is is getting declared as:
(parameter) result: {
id: number;
name: string;
} | {
data: {
id: number;
name: string;
};
meta: {
from: number;
to: number;
total: number;
last_page: number;
};
} | undefined
Instead of:
(parameter) result: {
id: number;
name: string;
} | undefined
First of all, you're trying to correlate your response type based on your params
type, but there's nothing in your or axios
's code which makes them at all related. Params are just an independent dictionary of properties and aren't related to the returned response type at all, so any checks on params
(or params?.paginate
in your example) won't make Typescript narrow down the response type.
In fact, the relationship between params
and response type seems to be a contract of your own, meaning that you're making assumptions about the API that you're consuming - you can obviously have an endpoint in the wild which completely ignores params.paginate
and returns the data in an arbitrary format, at which point your code will simply break, even if you managed to get around the compilation errors. The real question is whether you'd actually like to keep this contract for ALL of your endpoints which you'll be calling via your own axiosGet
wrapper.
I would suggest to review the API design first and perhaps make paginated and non-paginated response structure more similar. For example, you could keep this format:
export type TypeAxiosData<TData> = {
data: TData
meta?: {
from: number
to: number
total: number
last_page: number
}
}
This way you can still expect some common format from all of your endpoints (which is much easier to implement via some global middleware), but your paginated and non-paginated responses are much more similar.
Finally, I wouldn't rely on the pagination metadata to be present in the response, even if you asked for it in the params - the only way to make sure if it's actually present is to check the response payload. And believe me, it's much cheaper to check for the metadata presence in runtime than to assume it's always there on paginated endpoints, only to find out that you forgot to add it to one of those endpoints and now the things are crashing silently. This is why in the code example above I skipped the pagination conditional.
If you REALLY want to make your response type dependent on the params, then you need to pass the params as the generic type to the response and make params
generic in TypeAxiosArgs
too.