Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to type axios error in Typescript? #3612

Closed
EkeMinusYou opened this issue Jan 30, 2021 · 18 comments
Closed

How to type axios error in Typescript? #3612

EkeMinusYou opened this issue Jan 30, 2021 · 18 comments

Comments

@EkeMinusYou
Copy link

EkeMinusYou commented Jan 30, 2021

Describe the issue

I have question how type axios error in Typescript.
I know AxiosError type is exposed, but I don't think it should be used because it's unclear if it's an Axios Error when caught.

Example Code

import axios, {AxiosError} from 'axios';
axios.get('v1/users')
  .catch((e: AxiosError) => { // really AxiosError?
      console.log(e.message);
  }

Should I do something judgement before typing with AxiosError?
Please tell me a good practice.

Expected behavior, if applicable

A clear and concise description of what you expected to happen.

Environment

  • Axios Version [e.g. 0.18.0]
  • Adapter [e.g. XHR/HTTP]
  • Browser [e.g. Chrome, Safari]
  • Browser Version [e.g. 22]
  • Node.js Version [e.g. 13.0.1]
  • OS: [e.g. iOS 12.1.0, OSX 10.13.4]
  • Additional Library Versions [e.g. React 16.7, React Native 0.58.0]

Additional context/Screenshots

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

@timemachine3030
Copy link
Contributor

@EkeMinusYou This is a great question. If you look at the types you'll see that AxiosError has a property isAxiosError that is used to detect types, when combined with the builtin typeguard:

.catch((err: Error | AxiosError) {
  if (axios.isAxiosError(error))  {
    // Access to config, request, and response
  } else {
    // Just a stock error
  }
})

This is a newer feature, so I suggest you update to the newest version of Axios to ensure it is present.

@EkeMinusYou
Copy link
Author

@timemachine3030 It looks good. Thank you!

@m4ttheweric
Copy link

m4ttheweric commented Aug 4, 2021

just in case this helps anyone else I created some middleware based on this answer to make the code a bit cleaner in the eventual request:

import axios, { AxiosError } from 'axios';

interface IErrorBase<T> {
   error: Error | AxiosError<T>;
   type: 'axios-error' | 'stock-error';
}

interface IAxiosError<T> extends IErrorBase<T> {
   error: AxiosError<T>;
   type: 'axios-error';
}
interface IStockError<T> extends IErrorBase<T> {
   error: Error;
   type: 'stock-error';
}

export function axiosErrorHandler<T>(
   callback: (err: IAxiosError<T> | IStockError<T>) => void
) {
   return (error: Error | AxiosError<T>) => {
      if (axios.isAxiosError(error)) {
         callback({
            error: error,
            type: 'axios-error'
         });
      } else {
         callback({
            error: error,
            type: 'stock-error'
         });
      }
   };
}

Then the code looks like:

.catch(
   axiosErrorHandler<MyType>(res => {
      if (res.type === 'axios-error') {
         //type is available here
         const error = res.error;
      } else {
         //stock error
      }
   })
);

@mtt87
Copy link

mtt87 commented Oct 4, 2021

I have a scenario where the error response is different than the successful response and I'm trying to figure out what's the best way to type it.
So far I came up with this but not sure it's the best thing to do, I would appreciate your feedback 😄
https://codesandbox.io/s/upbeat-easley-ljgir?file=/src/index.ts

import request from "axios";

type TodoSuccessResponse = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
};

type TodoErrorResponse = {
  error: string;
};

async function main() {
  try {
    const res = await request.get<TodoSuccessResponse>(
      "https://jsonplaceholder.typicode.com/todos/1"
    );
    console.log(res.data.id);
  } catch (err) {
    if (request.isAxiosError(err) && err.response) {
      // Is this the correct way?
      console.log((err.response?.data as TodoErrorResponse).error);
    }
  }
}

@askariali
Copy link

It worked for me:

try {
  // statements
} catch(err) {
  const errors = err as Error | AxiosError;
  if(!axios.isAxiosError(error)){
    // do whatever you want with native error
  }
  // do what you want with your axios error
}

@btraut
Copy link

btraut commented Jul 28, 2022

axios.isAxiosError is useful, but it doesn't type-guard or allow you to specify a generic response type. I added a really simple util function in my codebase to help with those:

export function isAxiosError<ResponseType>(error: unknown): error is AxiosError<ResponseType> {
  return axios.isAxiosError(error);
}

You can then use it to get fully-typed error response data:

type MyExpectedResponseType = {
  thisIsANumber: number;
};

try {
  // Make the axios fetch.
} catch (error: unknown) {
  if (isAxiosError<MyExpectedResponseType>(error)) {
    // "thisIsANumber" is properly typed here:
    console.log(error.response?.data.thisIsANumber);
  }
}

@janusqa
Copy link

janusqa commented Oct 27, 2022

axios.isAxiosError is useful, but it doesn't type-guard or allow you to specify a generic response type. I added a really simple util function in my codebase to help with those:

export function isAxiosError<ResponseType>(error: unknown): error is AxiosError<ResponseType> {
  return axios.isAxiosError(error);
}

You can then use it to get fully-typed error response data:

type MyExpectedResponseType = {
  thisIsANumber: number;
};

try {
  // Make the axios fetch.
} catch (error: unknown) {
  if (isAxiosError<MyExpectedResponseType>(error)) {
    // "thisIsANumber" is properly typed here:
    console.log(error.response?.data.thisIsANumber);
  }
}

I am having a little problem understanding this.
MyExpectedResponseType is the expected data type to get back from a successful response?
Why do we want to access it in a failed response. I thought response.data would hold information on why it failed. For example data submitted to request was incorrect and it responds with which data field failed. That would be a different type to say the data type of a successful response

@glspdotnet
Copy link

axios.isAxiosError is useful, but it doesn't type-guard or allow you to specify a generic response type. I added a really simple util function in my codebase to help with those:

export function isAxiosError<ResponseType>(error: unknown): error is AxiosError<ResponseType> {
  return axios.isAxiosError(error);
}

You can then use it to get fully-typed error response data:

type MyExpectedResponseType = {
  thisIsANumber: number;
};

try {
  // Make the axios fetch.
} catch (error: unknown) {
  if (isAxiosError<MyExpectedResponseType>(error)) {
    // "thisIsANumber" is properly typed here:
    console.log(error.response?.data.thisIsANumber);
  }
}

I am having a little problem understanding this. MyExpectedResponseType is the expected data type to get back from a successful response? Why do we want to access it in a failed response. I thought response.data would hold information on why it failed. For example data submitted to request was incorrect and it responds with which data field failed. That would be a different type to say the data type of a successful response

I'd guess that this is useful in the case of known/expected types of Axios errors. For example, I use the following to provide type info to Laravel validation error responses (always status code 422 with message & errors data properties):

interface LaravelValidationResponse extends AxiosResponse {
    status: 422;
    data: {
        message: string;
        errors: Record<string, Array<string>>;
    };
}

export interface LaravelValidationError extends AxiosError {
    response: LaravelValidationResponse;
}

function axiosResponseIsLaravelValidationResponse(response: AxiosResponse): response is LaravelValidationResponse {
    return response.status === 422
        && typeof response.data?.message === 'string'
        && typeof response.data?.errors === 'object';
}

export function isLaravelValidationError(error: unknown): error is LaravelValidationError {
    return Boolean(
        axios.isAxiosError(error)
        && error.response
        && axiosResponseIsLaravelValidationResponse(error.response)
    );
}

While I probably wouldn't go with @btraut's approach as it doesn't allow for differentiating between different types of errors, it could be useful as a quick type cast.

@damisparks
Copy link

@EkeMinusYou This is a great question. If you look at the types you'll see that AxiosError has a property isAxiosError that is used to detect types, when combined with the builtin typeguard:

.catch((err: Error | AxiosError) {
  if (axios.isAxiosError(error))  {
    // Access to config, request, and response
  } else {
    // Just a stock error
  }
})

This is a newer feature, so I suggest you update to the newest version of Axios to ensure it is present.

This is fantastic and very helpful. Thank you very much 🙏🏾

@RefaelAm
Copy link

RefaelAm commented Dec 27, 2022

@EkeMinusYou This is a great question. If you look at the types you'll see that AxiosError has a property isAxiosError that is used to detect types, when combined with the builtin typeguard:

.catch((err: Error | AxiosError) {
  if (axios.isAxiosError(error))  {
    // Access to config, request, and response
  } else {
    // Just a stock error
  }
})

This is a newer feature, so I suggest you update to the newest version of Axios to ensure it is present.

Hi there @timemachine3030,
What is the Error type? A type you created?

@timemachine3030
Copy link
Contributor

Error is defined by the typescript libs for your target (in your tsconfig.json file). For example: https://github.com/microsoft/TypeScript/blob/main/lib/lib.es5.d.ts#L1052

Revisiting this after a year for learning more about ts/js I would rewrite it as:

.catch((err: unknown) {
  if (axios.isAxiosError(error))  {
    // Access to config, request, and response
  } else if (err instanceof Error) {
    // Just a stock error
  } else {
    // unknown
  }
})

Assuming errors are of created by new Error(...) in javascript is a bad practice. You can technically throw anything. throw "a string"; is valid, unfortunately.

The browser and node.js can throw native errors that don't extend the Error class. For example, GeolocationPositionError is not an Error as it may not contain a stack trace.

@Felipeex
Copy link

my error interface.

interface Error {
  message: string[];
  statusCode: number;
}

my catch error.

try {
} catch (err) {
  const error = err as AxiosError<Error>;
  console.log(error.response?.data.message);
}

@bennycode
Copy link
Contributor

To identify an AxiosError in the catch clause, the isAxiosError function can be used. I made a video tutorial about it: https://www.youtube.com/watch?v=NGSck4aHfeQ

@gavinthomas-valtech
Copy link

I tried to add a generic to isAxiosError but i get

image

@gavinthomas-valtech
Copy link

Looks like i need to upgrade axios package

@kellyrmilligan
Copy link

I would love something more concise, like axios.get<ResponseDataType, ErrorType> to allow types to just flow through?

@cbserra
Copy link

cbserra commented Sep 26, 2023

I would love something more concise, like axios.get<ResponseDataType, ErrorType> to allow types to just flow through?

If you don't mind adding another dependency to your project, the axios-hooks module has some types and interfaces that do just that:

export interface Options {
  manual?: boolean
  useCache?: boolean
  ssr?: boolean
  autoCancel?: boolean
}

export interface RefetchOptions {
  useCache?: boolean
}

export interface ConfigureOptions {
  axios?: AxiosInstance | AxiosStatic | any
  cache?: LRUCache<any, any> | false
  defaultOptions?: Options
}

export type UseAxiosResult<TResponse = any, TBody = any, TError = any> = [
  ResponseValues<TResponse, TBody, TError>,
  (
    config?: AxiosRequestConfig<TBody>,
    options?: RefetchOptions
  ) => AxiosPromise<TResponse>,
  () => void
]

export interface UseAxios {
  <TResponse = any, TBody = any, TError = any>(
    config: AxiosRequestConfig<TBody> | string,
    options?: Options
  ): UseAxiosResult<TResponse, TBody, TError>

  loadCache(data: any[]): void
  serializeCache(): Promise<any[]>

  configure(options: ConfigureOptions): void
  resetConfigure(): void
  clearCache(): void

  // private
  __ssrPromises: Promise<any>[]
}

...

export function makeUseAxios(options?: ConfigureOptions): UseAxios

Just call the makeUseAxios function, passing necessary config. Check the site for plenty of examples. Pretty easy to setup, and it solved the same problem you had; where the API had a Success response and an Error response (that's what I'm guessing your scenario is).

Good luck

@tao-tiago
Copy link

`
import axios, { AxiosError } from "axios"

try {

  const { data } = await axios.get<MyTypeReturn>(requestURI, {
    headers: {
      Authorization: `Bearer ${FEDERAL_TOKEN}`
    }
  })

  return data

} catch (error) {

  let message = "Generic message error"
  let code = 500

  if (error instanceof AxiosError) {
    message = error.response?.data.message || "Server Unavailable"
    code = error.response?.status || 503
  }

  if (error instanceof Error) {
    message = error.message
  }
}

`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

17 participants