axios: Typescript types do not make sense

Describe the bug

I updated from axios 0.21.1 to 0.22.0 but typescript started giving me strange errors

I then noticed that the types declarations changed and they do not make much sense.

I can see that the post signature is:

post<T = never, R = AxiosResponse<T>>(url: string, data?: T | undefined, config?: AxiosRequestConfig<T> | undefined): Promise<R>

I don’t really get why the response data object should be, by default, equal to the object passed as body, there is really no sense for that as a default behaviour. It should be AxiosResponse<any> instead.

For the axios function, the returned object is even a never, in axios(config: AxiosRequestConfig<any>): AxiosPromise<never>.

Of course I can just put the types specifications, but it makes the code longer and ugly when there would be many times no reason for that.

To Reproduce

To reproduce it, just try to make a post request

const response = await axios.post(
                CONFIG.API_UNITN.CLESIUS.SECURITY.AUTH_SERVICE_URL,
                data.toString()
            ); // the response has AxiosResponse<string> type

Expected behavior

There should not be types assumptions such as the one made

Environment

  • Axios Version 0.22.0
  • Adapter http
  • Browser any
  • Browser Version any
  • Node.js Version any
  • OS: Linux Ubuntu 20
  • Additional Library Versions none

Additional context/Screenshots

Add any other context about the problem here. If applicable, add screenshots to help explain.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 39
  • Comments: 18 (3 by maintainers)

Most upvoted comments

You certainly can solve this issue by adding any Like so:

const { data }: any = await instance.get("/users/me");

Glad to see I’m not the only one with it! It’d be nice to see what the axios team think to this issue.

I’ve also had issues adding interceptors - also type issues:

(config: any) => {
    const token = useStore.getState().authToken;

    if (token) {
      config.headers.jwtAuthToken = token;
    } else {
      if (instance && instance.defaults.headers) {
        delete instance.defaults.headers.jwtAuthToken;
      }
    }
    return config;
  },

without (config:any) there, config no longer believes it should have a headers property 😐

Yeah the next release should mend this, will probably release in the next hour or so

I think the typings for axios.post is simply wrong, as @euberdeveloper pointed out, assuming response data is the same shape as post params makes no sense.

After upgrading from 0.21.1 to 0.22.0, my existing codebase is showing Typescript errors now. The return type from axios.get() or event just axios() is now returning a response with never as the type??

This code used to work without problems on 0.21.1:

const writeableStream = createWriteStream(path);
const response = await axios({method:'get',url:url, responseType: 'stream' });
response.data.pipe(writeableStream);
response.data.on('end', () => resolve(path));
response.data.on('error', (err) => reject(err));

Now with 0.22.0 I get this:

image

Property 'pipe' does not exist on type 'never'

Something got messed up with the typings for the response… I have to downgrade to 0.21.1 for now. Wish I could help troubleshoot this but it’s beyond my skills.

This has been fixed in v.0.23.0 by https://github.com/axios/axios/pull/4116. Can be closed as duplicate.

post<T = never, R = AxiosResponse<T>>(url: string, data?: T, config?: AxiosRequestConfig<T>): Promise<R>;

that means post() returns a promise which has a type of AxiosResponse<T>. T means the type of request body.

maybe we can temporary solve this problem by using any

await axios.post(url, data).then((resp: AxiosResponse<any>) => { do something... })

AxiosResponse reverted to any in v0.24.0

Thanks! I pulled in v0.24.0 and my code is back to working as before in v0.21.4.

This should be mended in the latest version

this is actually my current issue. and I thought maybe I’m mistaken. and for the past two hours, I was searching the web for a possible fix. Does anyone have any suggestions?

downgrade to 0.21.1, works again.

I have another problem with the interceptor typings: they do not allow for a 3rd ‘options’ parameter, despite what the documentation says : https://github.com/axios/axios#interceptors.

I dont know how to type interceptors anymore. I have the following:

type TokenResponse = {
  token?: string;
};

client.interceptors.response.use(async (response: AxiosResponse<TokenResponse>) => {
  // Some code
  return response.data;
});

But I always got the following error:

Argument of type ‘(response: AxiosResponse) => Promise’ is not assignable to parameter of type ‘(value: AxiosResponse<unknown, any>) => unknown’. Types of parameters ‘response’ and ‘value’ are incompatible. Type ‘AxiosResponse<unknown, any>’ is not assignable to type ‘AxiosResponse<TokenResponse, any>’. Type ‘unknown’ is not assignable to type ‘TokenResponse’.

Ah, this looks like a legit bug with how the AxiosInterceptorManager is typed:

export interface AxiosInterceptorManager<V> {
  use<T = V>(onFulfilled?: (value: V) => T | Promise<T>, onRejected?: (error: any) => any): number;
  eject(id: number): void;
}

export class Axios {
  constructor(config?: AxiosRequestConfig);
  defaults: AxiosDefaults;
  interceptors: {
    request: AxiosInterceptorManager<AxiosRequestConfig>;
    response: AxiosInterceptorManager<AxiosResponse>;
  };
// ...rest of methods
}

There’s no way to inject the type of response you want to intercept, it defaults to unknown and that’s it.

This might be correct typing, though, since the response interceptors see all types of data coming through.

Regardless, you are correct, there’s no way to type that signature, and forcing everyone to write type guards to narrow down from unknown to some type you trust is perhaps not ideal.

If I understand the axios docs correctly this signature for AxiosInterceptorManager should do the trick.

export interface AxiosInterceptorManager<V extends AxiosResponse | AxiosRequestConfig> {
  use<T, D = any>(
    onFulfilled?: (value: V extends AxiosResponse ? AxiosResponse<T, D> : AxiosRequestConfig<T>) => typeof value | Promise<typeof value>,
    onRejected?: (error: any) => any
  ): number;
  eject(id: number): void;
}

That being said one should still call the interceptor like so:

type ResponseTypeA = {
  token?: string;
};

type ResponseTypeB = {
  token?: number;
};

type ResponseTypeC = {
  token?: Array<number>;
};

this.client.interceptors.response.use<
  ResponseTypeA | ResponseTypeB | ResponseTypeC
>(async (response) => {
  // Some code
  return response;
});

Because, literally, all types of responses pass through your interceptor.

So, maybe I faced a similar problem:

public async getContestTypeByName(name: string): Promise<ContestType> {
    return new Promise<ContestType>((resolve, reject) => {
      this.api
        .get(`${Endpoints.ContestType}/${name}`)
        .then((response: AxiosResponse<ContestType>) => resolve(response.data)) // ts error in this line
        .catch((error: AxiosError<string>) => reject(error));
    });
  }

TypeScript Error log:

Argument of type '(response: AxiosResponse<ContestType>) => void' is not assignable to parameter of type '(value: AxiosResponse<unknown, any>) => void | PromiseLike<void>'.
  Types of parameters 'response' and 'value' are incompatible.
    Type 'AxiosResponse<unknown, any>' is not assignable to type 'AxiosResponse<ContestType, any>'.
      Type 'unknown' is not assignable to type 'ContestType'.ts(2345)

Fixed by typing the axios.get method itself:

public async getContestTypeByName(name: string): Promise<ContestType> {
    return new Promise<ContestType>((resolve, reject) => {
      this.api
        .get<ContestType>(`${Endpoints.ContestType}/${name}`)  //  <- here
        .then((response) => resolve(response.data))
        .catch((error: AxiosError<string>) => reject(error));
    });
  }

Don’t know if it’s the right way, but works fine. Reference