axios: AxiosRequestHeaders type breaks from 0.21.4 to 0.22.0

Describe the bug

The type has changed and now I am getting linting errors on a working code. I get Element implicitly has an ‘any’ type because index expression is not of type ‘number’.ts(7015) error when I try to access the headers by their name on a AxiosRequestHeaders object.

For example accessing a common header in transfomRequest where the content of headers is: image

If I try to access the common Accept header key, then I will get the above error.

I think the issue is at: https://github.com/axios/axios/blob/1025d1231a7747503188459dd5a6d1effdcea928/index.d.ts#L3

The type should be:

export type AxiosRequestHeaders = Record<string, Record<string, string>>; 

Possibly the AxiosResponseHeaders has the same issue.

To Reproduce

import axios from 'axios'

axios({
    method: 'post',
    url: '/user/12345',
    data: {
        firstName: 'Fred',
        lastName: 'Flintstone',
    },
    transformRequest: (_, headers) => {
        if(headers)
            delete headers.common['Accept'] //Error occurs here
    },
})
    .then(function (response) {
        console.log(response)
    })
    .catch(function (error) {
        console.log(error)
    })

Expected behavior

Expected to not raise an error there

Environment

  • Axios 0.23.0
  • TypeScript 4.4.4

Additional context/Screenshots

image

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 27
  • Comments: 33 (1 by maintainers)

Most upvoted comments

Still having this issue with version 0.26.0

Element implicitly has an 'any' type because expression of type '"X-XSRFToken"' can't be used to index type 'string | number | boolean'.
  Property 'X-XSRFToken' does not exist on type 'string | number | boolean'.ts(7053)
Element implicitly has an 'any' type because expression of type '"Content-Type"' can't be used to index type 'string | number | boolean'.
  Property 'Content-Type' does not exist on type 'string | number | boolean'.ts(7053)

Hey there, any one tries to add headers on interceptors will probably have type errors. This because of headers property was any previously at version 0.21.1 but after 0.21.4 till 0.22.7 they have added type to headers property and it brakes typescript user’s heart.

An image from 0.21.1 Screen Shot 2022-06-16 at 23 50 02

Here is a simple solution


interface MyAxiosRequestConfig extends Omit<AxiosRequestConfig, "headers"> {
	headers?: any; // this was "any" at v0.21.1 but now broken between 0.21.4 >= 0.27.2
        // Lets make it any again to make it work again.
}

export const onRequest = async (req: MyAxiosRequestConfig): Promise<MyAxiosRequestConfig> => {
   // do any additional header adds
   req.headers.common["Authorization"] = `Bearer ${token}`;
}

Yep I am still unable to do this in 0.24.0

apiClient.interceptors.request.use(async (config) => {
  config.headers.common = config.headers.common ?? {};
  config.headers.common.Authorization = `Bearer ${token}`;

  return config;
});

I am getting Type 'string | {}' is not assignable to type 'string'. Type '{}' is not assignable to type 'string'.ts(2322)

I have this issue when trying to set the default headers thus:

if (axios.defaults.headers != null) {
  axios.defaults.headers.common['X-CSRFToken'] = csrftoken
}

so setting the AxiosRequestTransformer to use HeadersDefaults wouldn’t fix that?

This is still broken in 0.23.0. Our specific use case is that we create instances of axios and want to set the common request headers:

export default function configuredAxios(locale: string): AxiosInstance {
    const axiosRequestConfig: AxiosRequestConfig = {
        headers: {
            common: {
                'Accept-Language': locale
            }
        }
    }

    return axios.create(axiosRequestConfig)
}

This breaks because in AxiosRequestConfig, headers is now of type AxiosRequestHeaders, which is just a type alias for Record<string, string>, so it doesn’t allow nested properties. But at this location, nesting in common, get, post, etc. is perfectly fine.

Still having this issue with version 0.25.0

Axios has changed the defaults object. Now we can specify defaults per method. To assign to every HTTP method, just use:

axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;

Example now:

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';`

Documentation: https://axios-http.com/docs/config_defaults

I got similar error in version 27.2. My solution was like below.

`import axios, { HeadersDefaults } from ‘axios’;

interface CommonHeaderProps extends HeadersDefaults { “x-validation-key”: string; “x-validation-host”: string; };

axios.defaults.headers = { “x-validation-key”: validationKey, “x-validation-host”: validationHost, } as CommonHeaderProps;`

This should be fixed with the next release, a pre-release should be up a bit later today

Still doesn’t fix the issue. Issue isn’t assigning defaults.

The issue is you cannot define expected response headers (say from an API endpoint with documented header responses that are HeadersDefaults + more stuff)

This line is the issue: https://github.com/axios/axios/blob/ee151a7356ec4498af045dd830312822637890c9/index.d.ts#L115

Fix:

export interface HeadersDefaults<T = any> {
  common: T | AxiosRequestHeaders;
  delete: AxiosRequestHeaders;
  get: AxiosRequestHeaders;
  head: AxiosRequestHeaders;
  post: AxiosRequestHeaders;
  put: AxiosRequestHeaders;
  patch: AxiosRequestHeaders;
  options?: AxiosRequestHeaders;
  purge?: AxiosRequestHeaders;
  link?: AxiosRequestHeaders;
  unlink?: AxiosRequestHeaders;
}

Then in my implementation:


interface MyRequiredHeaders {
  authorization: AxiosRequestHeaders;
  cookie: AxiosRequestHeaders;
  'og-hmac': AxiosRequestHeaders;
  'x-csrftoken': AxiosRequestHeaders;
}

type MyRequiredHeaderDefaults = HeadersDefaults< MyRequiredHeaders >;

  /*

    Now the following works as expected

  */

  const headers: RC3HeadersDefaults = rc3.defaults.headers;
  headers.common.authorization = `Bearer ${token}`;
  headers.common.cookie = authCookie;
  headers.common['og-hmac'] = hmac;
  if (cookieData.csrftoken)
    headers.common['x-csrftoken'] = cookieData.csrftoken;

Screenshot in VSCode

image

I believe the main issue is related to this Omit:

export interface AxiosDefaults<D = any> extends Omit<AxiosRequestConfig<D>, 'headers'> {
  headers: HeadersDefaults;
}

My issue is actually resolved if i use headers.common prior to that I was trying to set axios.defaults.header['thing'] = 'foo';

@Jesus-Escalona your last example works correctly at runtime, it’s only a typing issue. The types need to be fixed accordingly.

+1 on this

May I ask what is the preferred way to instantiate an axios class while setting its headers?

The docs here say it should be done as:

const instance = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

Using this approach I get my custom headers intermingled with the HeadersDefaults i.e: common, delete, get, head, post …

However, the docs here say one should set the custom header defaults as:

// Set config defaults when creating the instance
const instance = axios.create({
  baseURL: 'https://api.example.com'
});

// Alter defaults after instance has been created
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;

One would think that you could do something like:

axios.create({headers: {common: headerData}})

But currently getting object is not assignable to type 'string'.

Note that there is also this example in the README:

const instance = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

In this case the custom header will be at the root of the headers object:

console.log(instance.defaults.headers)

{
  "X-Custom-Header": "foobar",
  common: { Accept: "application/json, text/plain, */*" },
  delete: {  },
  get: {  },
  head: {  },
  patch: { "Content-Type": "application/x-www-form-urlencoded" },
  post: { "Content-Type": "application/x-www-form-urlencoded" },
  put: { "Content-Type": "application/x-www-form-urlencoded" },
}

I am not sure if this is the documentation issue and it should be headers: { common: 'X-Custom-Header': 'foobar'} } or a type issue and HeaderDefaults should also allow arbitrary keys in the root.

I could be wrong but I think a better fit would be to change the AxiosRequestTransformer to use HeadersDefaults instead. That specifically has properties such as common, delete, get, and more. It has the added benefit of not breaking any existing use cases in the typescript tests.

I think you are right. HeadersDefaults would be a better fit for the headers parameter’s type in AxiosRequestTransformer.

Your code at https://github.com/axios/axios/issues/4193#issuecomment-945204765 works. It should would work with the transformers too then. It is also stricter, which is always good.

axios.defaults.headers.common doesn’t reference AxiosRequestTransformer.

That’s correct, but it does reference AxiosRequestHeaders, which is what causes the issue. Unless I’m misreading the original bug report, one of the effects of the change is to cause an issue with the AxiosRequestTransformer, but it also causes an issue with any use of the AxiosRequestHeaders type.

With respect, I think there’s a misunderstanding happening here - I’m contending that the issue isn’t specific to AxiosRequestTransformer, but all uses of the AxiosRequestHeaders type, which includes axios.defaults.headers