Wai Yan Hein
Wai Yan Hein

Reputation: 14831

React query mutation: getting the response from the server with onError callback when the API call fails

I am working on a React JS project. In my project, I am using React query, https://react-query.tanstack.com/docs/guides/mutations. I am using mutation to make the post request to the server. But I am trying the get the response returns from the server when the API call fails with the onError call back.

This is my code.

let [ createItem ] = useMutation(payload => createItem(payload), {
    onSuccess: (response) => {
      
    },
    onError: (error) => {
      // here I am trying to get the response. In axios, we can do something like error.data.server_error_code
    },
    onMutate: () => {
      
    }
  })

As you can see in the comment, I am trying to read a field returned from the server within the onError callback. How can I do that?

Upvotes: 13

Views: 30249

Answers (6)

Rabah Ali Shah
Rabah Ali Shah

Reputation: 71

The easiest fix is to register the Global Error object. Let's say you are fetching your data using Axios with useMutation hook then simply declare the type as below:

import axios, { AxiosError } from 'axios';

// changing the defaultError type
declare module '@tanstack/react-query' {
  interface Register {
    defaultError: AxiosError;
  }

const {data, error} = useMutation({
    mutationFn: (body) => axios.post("your_api", body),
    onError(error) {
        console.log(error?.response?.data.detail)
    },
  });
}

console.log(error?.response?.data.detail)

For more detail read: Tanstack Query | Typescript

Upvotes: 0

Huynh Triet
Huynh Triet

Reputation: 181

This is how I capture the error message in the React app with react-query. For example, this is the payload my API sends back for the error

{ 
 statusCode: ERROR_CODE, 
 description: "YOUR_ERROR_MESSAGE" 
}

I am using axios for fetching API data, and I am using Typescript, so I would need to cast my error first to an instance of AxiosError class before extracting the message

onError: (error) => {
        if (error instanceof AxiosError) {
          console.error(error.response?.data.description)
        } else {
          console.error('GENERIC_ERROR_MESSAGE')
        }
      }

Hope this helps!

Upvotes: 3

Michael Simonitsch
Michael Simonitsch

Reputation: 73

I think the issue with NOT having an error.response in the callback depends on how the API is failing. If you look at the react-query documentation it shows that most HTTP libs like axios will throw if there is a non 2xx response. However it's up to the underlying API function how it handles that.

For example axios https://axios-http.com/docs/handling_errors will return the response object if there is a response from the server. They will return the request if the call has timed out and return just a message if the previous two don't fit the error

 axios.get('/user/12345')
  .catch(function (error) {
    if (error.response) {
      // The request was made and the server responded with a status code
      // that falls out of the range of 2xx
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // The request was made but no response was received
      // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
      // http.ClientRequest in node.js
      console.log(error.request);
    } else {
      // Something happened in setting up the request that triggered an Error
      console.log('Error', error.message);
    }
    console.log(error.config);
  });

However, if you're using the Fetch API you have handle this yourself. Taken straight from react-query's docs: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default

useQuery(['todos', todoId], async () => {
   const response = await fetch('/todos/' + todoId)
   if (!response.ok) {
     throw new Error('Network response was not ok')
   }
   return response.json()
 })

Upvotes: 0

Emmanuel Orozco
Emmanuel Orozco

Reputation: 389

If you are using fetch, you have to know that fetch does not throw any error unless is a network problem (as read here)

My solution was just to change to axios (which throws error when 400 or 500), but if you still need to use fetch, you need to find a way to make it throw errors instead.

Upvotes: 1

Ihor
Ihor

Reputation: 163

It should work as it is. Make sure that your HTTP client (probably, Axios) is configured to throw an error. For example:

import axios from 'axios'
import { useMutation } from 'react-query'
import { BASE_URL } from 'constants/api'

const client = axios.create({
  baseURL: BASE_URL,
})

const request = (options) => {
  const onSuccess = (response) => response
  const onError = (error) => {
    // Throwing an error here
    throw error
  }
  return client(options).then(onSuccess).catch(onError)
}

const { mutate } = useMutation(
  async (data) =>
    await request({
      url: '/someUrl',
      method: 'post',
      data
    }),
    { onError: (e) => console.log(e) }
  )

And of course, it's better to store your Axios settings within a separate file, and then just import the 'request' variable where mutations are using.

Upvotes: 1

caseyjhol
caseyjhol

Reputation: 3898

let [ createItem ] = useMutation(payload => createItem(payload), {
    onSuccess: (response) => {
      
    },
    onError: (error) => {
      console.log(error.response.data);
      console.log(error.response.status);
    },
    onMutate: () => {
      
    }
})

It's not entirely clear when just doing console.log(error) inside onError, but error.response should be available.

Upvotes: 14

Related Questions