In a project I am migrating to TypeScript (TS), I have a response interceptor r => r.data
. How do I inform TS that I am not expecting a type of AxiosResponse
? I've tried overriding using as Array<...
but that doesn't work as AxiosResponse
can't be cast as an Array
(e.g. does not has .length).
Thanks!
Use the axios.request<T>(...args)
style definition.
The last Response-interceptor in the array implicitly comply to an interface like (currentResponse: any) => T
So if you have data
being something like:
interface ServerResponse {
data: ServerData
}
interface ServerData {
foo: string
bar: number
}
Then you can say:
axios.request<ServerData>({
url: 'https://example.com/path/to/data',
transformResponse: (r: ServerResponse) => r.data
}).then((response) => {
// `response` is of type `AxiosResponse<ServerData>`
const { data } = response
// `data` is of type ServerData, correctly inferred
})
@zcei ooh actually this wouldn't work for global interceptors e.g. axios.interceptors.response.use
I think it does - the interceptors are currently very "relaxed" typed (aka any
), so you can just attach the response interceptor and have it (r: any): any => r.data
(which is basically as if you'd omit any typing in non-strict mode).
Only at the point where you call axios.request
you can configure it's generic, which then get's passed through as the type of the data
property.
(Actually I made a mistake in above code snippet, data
is actually AxiosResponse<ServerData>
which has a field called .data
with the type ServerData
- updated it)
@zcei hi sorry but this really doesn't work with my example, i've created a codesandbox to highlight this issue.
Having the same issue, the interceptor can essentially be boiled down to:
this.instance.interceptors.response.use(response => response.data);
Despite this, the return type of
this.instance.post<string>(...);
is AxiosPromise<string>
, which expects the data to be wrapped.
This is intentionally. You are supposed to transform the data in the interceptor, but not to hoist response keys.
I thought you would have a server response like
{
"data": {
"foo": 42
}
}
and wanted to get rid of the response.data.data
nesting. If you return response.data
in the interceptor, then you can later access it via response.data.foo
instead of response.data.data.foo
.
But accessing response.foo
wouldn't work, as this is the "root" response level that keeps track of other stuff, like the response code and alike.
Sorry, but I don't agree with your premise, at least not functionality-wise.
Without using the interceptor to unwrap the data property, I would have to do
this.instance.post<string>(...).then(response => response.data);
this.instance.get<number>(...).then(response => response.data);
in _every_ endpoint that I define. What's the point in all that code repetition?
Whenever a request is successful, which having a then
means, I don't really care about the response code etc. I only care about response codes if something _didn't_ work, and that's what error handlers are there for, both on the interceptor as well as on specific endpoints.
this.instance.interceptors.response.use(response => {
// Everything went well, pass only relevant data through
return response.data;
}, error => {
// Something went wrong, figure out how to handle it here or in a `.catch` somewhere down the pipe
});
Whenever a request is successful, which having a then means, I don't really care about the response code
Well, you might not care for any other information, but constraining to everyone using an HTTP client that you only care about the body is not really a solution.
There are enough legit use-cases for status codes & headers even on successful calls. Like making distinctions on 204
vs 200
, checking rate limit headers, extracting Link
headers for additional resources (pagination), etc. pp.
If you don't care, wrap Axios and throw away everything:
this.instance.request = <T>(...args) => {
return axios.request<T>(...args).then(({ data }) => data)
}
this.instance.request<string>({ method: 'post', ... }).then(data => { ... });
this.instance.request<number>({ method: 'get', ... }).then(data => { ... });
I don't see where you got the notion that everyone should be doing things the way I do them in one project. I'm simply trying to solve a problem of how to automatically unwrap data on every request and given that this issue was here before me, I'm not the only one with that problem.
Interceptors _seem_ like the correct place to do it, both by their name and their description in the readme (do something with every response). Handling 204
vs 200
etc and then passing the data would also make sense in an interceptor because then you get to do it in one central place.
In my opinion, the intuitive solution would be to return what ever you want from the interceptor, not have the library force a single way.
If you want to transform some fields in data:
this.instance.interceptors.response.use(response => {
response.data.foo = JSON.parse(response.data.bar);
return response;
});
If you want to just unwrap Axios' data:
this.instance.interceptors.response.use(response => response.data);
This would leave the choice of what to do to the developer, which in my opinion is better than a very strongly opinionated interceptor. You're free to disagree, of course.
You were reasoning about "the point in all that code repetition", so I just tried to explain why the information is necessary.
I can't really find a way on how you'd like to keep type safety in the interceptors and the response, if any interceptor could scramble the whole response without a common structure. :/
The response type for a request would need to become any
I guess and put the effort into developers hands to make sure they do the correct thing.
That to me at least sounds more error prone due to the relaxed typings, than accessing one property.
If you can come up with a PR that allows interceptors to overwrite the root response while keeping type safety for "the 80% use-cases", I'd be more than happy!
The thing is, both of the above examples I made already work the way I described, functionality wise, it is just the types that are wrong. If returning anything else than the (modified) response in the interceptor is wrong, I think it would be good to update the expected types there.
First it's interesting to know you are doing the same thing @Etheryte !
In our app the r => r.data
is the final response interceptor in the chain and we use others which rely on status codes to handle refresh tokens etc. but at a global level as there is no need for us to handle this for specific calls.
I understand that it might just not be possible to accommodate this use case with TS. However, @zcei it's undeniable that this is a legitimate way to use Axios as it is using an official part of the Axios API (interceptors) :).
Doh! I've been looking at the wrong code all the time 🤦♂️ and was in the transformResponse
section, not the interceptor - I'm so sorry!
How would you update the typings to account for the interceptor changing the return type?
Maybe something like this?
interface AxiosInstance {
request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
}
You'd call it like this:
axios.request<User, string>({
method: 'get',
url: '/user?id=12345'
})
.then((foo: string) => { // <-- you could leave out the type annotation here, it's inferred
console.log(foo)
})
Just tried that out locally and seems to do what you're looking for.
Maybe I can get a PR together in the evening to update that for all the methods.
@zcei That looks good! Defaulting to AxiosResponse of course makes sense 99% of the time 👍
Great to hear! Sorry again for my confusion 😓
@zcei not a problem! Out of interest what is the release cycle for Axios?
There is none - I still have some pending things for the v1 alpha (#1333) and in the meantime @nickuraltsev / @emilyemorehouse are doing releases whenever necessary.
Since v0.18
there has been some traction (including my favorite: scoping options to instances) so I guess they could cut a release. For more I'd just kindly invite you over to Gitter, so that we keep the issues on point 🙂
I’ve been hoping to get out a 0.19 release but last I checked, master was failing CI. I’d definitely like a more solid/regular release schedule once we get 1.0 to land.
meet the same problem. Are there any solutions?
@qidaneix you could try installing npm install axios/axios#master
until there is a release. #1605 should have fixed it. Would be nice to get some feedback, whether that really helps with real life use-cases and not just tests 🙂
@zcei i'll try it out tomorrow
@zcei May I ask when it will release?
+1 when 1.0.0 will release?
This will be a pre-1.0 release for sure. @Khaledgarbaya did you get added to NPM by Matt as well? Otherwise we need to be nice to the remaining maintainers releasing it 😉
Hey all. I'm currently managing the NPM releases. I'd love to issue another pre-1.0.0 release, but the Travis tests are failing and I haven't had a chance to fix them yet. Once they're fixed, I'm more than happy to get this out immediately 😬
@zcei I was not added to the npm repo, I can only merge changes
Is there any update on this issue, will there be a release in the near future, like month-two?
+1
❤️ if this could be released now
We are aware, but blocked currently. 🙁 You can get more information over here: https://github.com/axios/axios/issues/1657#issuecomment-410766031
Released as part of 0.19.0-beta.1.
This can be installed using npm install [email protected]
or npm install axios@next
Maybe something like this?
interface AxiosInstance { request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>; }
You'd call it like this:
axios.request<User, string>({ method: 'get', url: '/user?id=12345' }) .then((foo: string) => { // <-- you could leave out the type annotation here, it's inferred console.log(foo) })
Just tried that out locally and seems to do what you're looking for.
Maybe I can get a PR together in the evening to update that for all the methods.
User(T, the first generic param) seems not used, if I want to use custom return types, I looks strange 😕
axios.request<void, string>({
...
})
using void may be more clear.
@emilyemorehouse Not to sound ungrateful but 0.19-beta has been open for three months now, is there an ETA for a GA release? Our project has an issue which requires one of these fixes. I asked this in the Gitter channel but doesn't seem like maintainers respond over there...
Perhaps a better wording would be what are the issues that need to be resolved before a release and is there a place to track it? Since there seems to be a lot of interest, spreading the work out and having a clear understanding of the required work could help speed things up.
There's already a project milestone for 0.19 but the tickets listed haven't seen any change for several months.
const func1: any = () => { return axios.request(...) }
I override AxiosResponse
in my axios.d.ts
:
import axios from 'axios'
declare module 'axios' {
export interface AxiosResponse<T = any> extends Promise<T> {}
}
Bumping this issue. It works... okay when I just copied AxiosInstance definition to local typings, but the implemented solution is very verbose in my opinion, unless I'm doing something wrong (not a Typescript expert). Since I'm using a separate axios instance created with axios.create
and using this interceptor:
AxiosClient.interceptors.response.use(
response => response && response.data,
error => error && error.response && error.response.data
);
where response.data
always has this form:
export interface APIResponse<T = any> {
data: T;
message: string;
status: boolean;
}
it seems like I have to use AxiosClient.post
like this:
AxiosClient.post<EndpointAPIResponse, APIResponse<EndpointAPIResponse>>('/endpoint', { someData });
to have proper types in .then
. Am I doing something wrong here or should it really be THAT verbose? If not, it would be much better if I could just pass expected response schema when creating the instance, but I can't make it work:
export interface AxiosInstance<R> {
<T = any>(config: AxiosRequestConfig): Promise<R<T>>;
<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
defaults: AxiosRequestConfig;
interceptors: {
request: AxiosInterceptorManager<AxiosRequestConfig>;
response: AxiosInterceptorManager<R>;
};
getUri(config?: AxiosRequestConfig): string;
request<T = any>(config: AxiosRequestConfig): Promise<R<T>>;
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
head<T = any>(url: string, config?: AxiosRequestConfig): Promise<R<T>>;
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R<T>>;
}
export interface AxiosStatic extends AxiosInstance<AxiosResponse> {
create<T = AxiosResponse>(config?: AxiosRequestConfig): AxiosInstance<T>;
Cancel: CancelStatic;
CancelToken: CancelTokenStatic;
isCancel(value: any): boolean;
all<T>(values: (T | Promise<T>)[]): Promise<T[]>;
spread<T, R>(callback: (...args: T[]) => R): (array: T[]) => R;
}
It works fine with axios.create()
without a generic type or just axios
, but if I pass my interface like this:
const AxiosClient = axios.create<APIResponse>({
// ...
});
and then use it like this: AxiosClient.post<string>('/endpoint').then(value => value.data)
, value.data
has type... T. Plus the version above only works if I actually replace these typings in node_modules, otherwise it gets totally mixed up and I end up with some total disaster. Could anyone help me out with this?
Edit: okay, I guess it won't work because it's not possible to use generics in that way (so R<T>
when R is a generic type is not correct syntax but I guess WebStorm for some reason didn't highlight it for me); https://github.com/Microsoft/TypeScript/issues/1213 this, I assume, would solve it but no idea if it ever gets implemented.
const request = <T>(options: AxiosRequestConfig) => {
return axios.request<IResponse<T>>(options).then<T>(response => {
const data = response.data;
if (data.c !== '0') {
return Promise.reject(new Error(data.m || 'error'));
}
return data.d;
});
}
use:
request<IApiGetBrandGoodsInfoByIds>({
method: 'GET',
url: '/api/a/b',
});
@zcei Is this resolved yet? Am not able to get following to work:
// so I created an axios instance:
const instance = axios.create(someOptions);
// then used interceptors
instance.interceptors.response.use((res) => res.data.data); // server response will always have 'data'
// then when using the following to make a request
instance.get<string>(someURI); // suppose server response was {data: 'some message'}
// ^^ the above returns type AxiosPromise<string>. How do I get it to return type Promise<string> instead?
having same problem, i install version 0.19beta,also ts cannot parse the correct type
I override
AxiosResponse
in myaxios.d.ts
:import axios from 'axios' declare module 'axios' { export interface AxiosResponse<T = any> extends Promise<T> {} }
oh niupi
I use interceptor to do something like: response => response.data
. So I need to remove the AxiosResponse
wrapper completely.
I finally end up with:
import axios from 'axios'
declare module 'axios' {
export interface AxiosInstance {
request<T = any> (config: AxiosRequestConfig): Promise<T>;
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
}
}
In case someone doesn't know how to use it:
{
"compilerOptions": {
"typeRoots": [
"./node_modules/@types",
"./src/types/",
]
}
}
This works for me:
Api.boards.createBoard = jest.fn((name:string) => {
const mockBoardResult = {
created_on: mockBoardData.date,
name,
threads: [],
updated_on: mockBoardData.date,
_id: mockBoardData._id
};
return Promise.resolve({data:mockBoardResult}) as Promise<AxiosResponse<any>>
});
another way to override
import * as axios from 'axios'
declare module 'axios' {
interface AxiosInstance {
(config: AxiosRequestConfig): Promise<any>
}
}
i think we maybe should't add property to AxiosResponse
when use interceptors, because interceptors can be eject.
// @axios-exts/add-foo-to-resp
declare module 'axios' {
interface AxiosResponse<T = any> {
foo: string
}
}
const addFooToResp = (res: AxiosResponse) => {
res.foo = 'bar'
return res
}
export default addFooToResp
// Your Project
import axios from 'axios'
import addFooToResp from '@axios-exts/add-foo-to-resp'
var id = axios.interceptors.response.use(addFooToResp)
axios.get('xx').then(res => {
// have type defined
// because we use `declare module 'axios'` ts can infer type
console.log(res.foo)
})
// but if then eject?
axios.interceptors.response.eject(id)
axios.get('xx').then(res => {
// also have type defined, it's maybe not reasonable
console.log(res.foo)
})
why we use typescript? because we hope our project will be safely.
if some day we remove a property from base utils, we would like the code that references it to produce an error at compile time.
so, we want use interceptor add some property to AxiosResponse
and have type inference, it's contradictory, because there is no way to ensure that type inference can be updated when interceptor is eject
i think axios should tell user:
the interceptor should handle something that has no effect on AxiosResponse
, if you want to extends AxiosResponse
property and have type inference, you should like 'plugin'
// @axios-exts/add-foo-to-resp
declare module 'axios' {
interface AxiosResponse<T = any> {
foo: string
}
}
const addFooToResp = (res: AxiosResponse) => {
res.foo = 'bar'
return res
}
export default (axios) => {
axios.interceptors.response.use(addFooToResp)
}
// Your Project
import axios from 'axios'
import addFooToResp from '@axios-exts/add-foo-to-resp'
addFooToResp(axios)
it's not 100% safety, but it's safety than just use axios.interceptors.response.use
and i recommend axios desgin a plugin system, now we always see like
import axios from 'axios'
import wrapper from 'axios-cache-plugin'
let http = wrapper(axios, {
maxCacheSize: 15
})
export default http
use like wrapper
to connect a plugin to axios, every plugin don't have a common rule, it's not elegant enough. i think should like
import axios from 'axios'
import axiosCache from 'axios-cache-plugin'
axios.use(axiosCache, {
// some options
})
export axios
// axios-cache-plugin
export default (axios) => {}
// or
export default {
install(axios){}
}
// like Vue.use for Vue.js
Use the
axios.request<T>(...args)
style definition.
The last Response-interceptor in the array implicitly comply to an interface like(currentResponse: any) => T
So if you have
data
being something like:interface ServerResponse { data: ServerData } interface ServerData { foo: string bar: number }
Then you can say:
axios.request<ServerData>({ url: 'https://example.com/path/to/data', transformResponse: (r: ServerResponse) => r.data }).then((response) => { // `response` is of type `AxiosResponse<ServerData>` const { data } = response // `data` is of type ServerData, correctly inferred })
Pls Can U Tell Me How I Can Add Header And Config In Axios Request With tsx
@MoZiadAlhafez
declare module 'axios' {
interface AxiosRequestConfig {
useCache: boolean
}
}
But this is not recommended
This is a long story, but seems to have been solved in #1605. See https://github.com/axios/axios/issues/1510#issuecomment-396894600.
If I misunderstood something, better to open a new issue with references to here. Thanks.
Most helpful comment
I override
AxiosResponse
in myaxios.d.ts
: