54

I am working on Reactjs redux on front-end and Rails API as a back-end.

So now I call API with Fetch API method but the problem is I cannot get readable error message like what I got inside the network tabs

this is my function

export function create_user(user,userInfoParams={}) {

    return function (dispatch) {
        dispatch(update_user(user));

        return fetch(deafaultUrl + '/v1/users/',
            {
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                method: "POST",
                body: JSON.stringify(userInfoParams)
            })
            .then(function(response) {
                console.log(response);
                console.log(response.body);
                console.log(response.message);
                console.log(response.errors);
                console.log(response.json());
                dispatch(update_errors(response));

                if (response.status >= 400) {
                    throw new Error("Bad response from server");
                }
         
            })
            .then(function(json){
                console.log("succeed json re");
                // We can dispatch many times!
                // Here, we update the app state with the results of the API call.

                dispatch(update_user(json));

            });


    }
}

But when errors came I cannot figure out how to get readable response message like I got when I check on my browser network tabs

So this is what I got from the network tabs when I got errors.

enter image description here

My console

enter image description here

This is my rails code

def create
    user = User.new(user_params)
    if user.save
      #UserMailer.account_activation(user).deliver_now
      render json: user, status: 201
    else
      render json: { errors: user.errors }, status: 422
    end
  end

But I cannot find out how can I get that inside my function

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Varis Darasirikul
  • 3,907
  • 9
  • 40
  • 75

11 Answers11

80

Since the text is hidden inside promise within response object, it needs to be handled like a promise to see it.

fetch(bla)
    .then(res => {
      if(!res.ok) {
        return res.text().then(text => { throw new Error(text) })
       }
      else {
       return res.json();
     }    
    })
    .catch(err => {
       console.log('caught it!',err);
    });
    
Bharathwaaj
  • 2,627
  • 2
  • 22
  • 35
Martins Untals
  • 2,128
  • 2
  • 18
  • 31
  • 1
    Yes, `response.text()` and `response.json()` [both return promise objects](https://developer.mozilla.org/en-US/docs/Web/API/Response). – alttag Oct 02 '19 at 19:41
  • 5
    You will get error: res.text is not a function if you get 400 response from the server. To avoid it, you must use `try{...} catch{...}` function. – Anish Sapkota Jul 07 '20 at 13:54
17

Similar to your answer, but with a bit more explanation... I first check if the response is ok, and then generate the error from the response.text() only for the cases that we have a successful response. Thus, network errors (which are not ok) would still generate their own error without being converted to text. Then those errors are caught in the downstream catch.

Here is my solution - I pulled the core fetch function into a wrapper function:

const fetchJSON = (...args) => {
  return fetch(...args)
    .then(res => {
      if(res.ok) {
        return res.json()
      }
      return res.text().then(text => {throw new Error(text)})
    })
}

Then when I use it, I define how to handle my response and errors as needed at that time:

fetchJSON(url, options)
  .then((json) => {
    // do things with the response, like setting state:
    this.setState({ something: json })
  })
  .catch(error => {
    // do things with the error, like logging them:
    console.error(error)
  })
Sia
  • 8,894
  • 5
  • 31
  • 50
15

even though this is a bit old question I'm going to chime in.

In the comments above there was this answer:

const fetchJSON = (...args) => {
  return fetch(...args)
    .then(res => {
      if(res.ok) {
        return res.json()
      }
      return res.text().then(text => {throw new Error(text)})
    })
}

Sure, you can use it, but there is one important thing to bare in mind. If you return json from the rest api looking as {error: 'Something went wrong'}, the code return res.text().then(text => {throw new Error(text)}) displayed above will certainly work, but the res.text() actually returns the string. Yeah, you guessed it! Not only will the string contain the value but also the key merged together! This leaves you with nothing but to separate it somehow. Yuck!

Therefore, I propose a different solution.

fetch(`backend.com/login`, {
   method: 'POST',
   body: JSON.stringify({ email, password })
 })
 .then(response => {
   if (response.ok) return response.json();
   return response.json().then(response => {throw new Error(response.error)})
 })
 .then(response => { ...someAdditional code })
 .catch(error => reject(error.message))

So let's break the code, the first then in particular.

.then(response => {
       if (response.ok) return response.json();
       return response.json().then(response => {throw new Error(response.error)})
})

If the response is okay (i.e. the server returns 2xx response), it returns another promise response.json() which is processed subsequently in the next then block.

Otherwise, I will AGAIN invoke response.json() method, but will also provide it with its own then block of code. There I will throw a new error. In this case, the response in the brackets throw new Error(response.error) is a standard javascript object and therefore I'll take the error from it.

As you can see, there is also the catch block of code at the very end, where you process the newly thrown error. (error.message <-- the error is an object consisting of many fields such as name or message. I am not using name in this particular instance. You are bound to have this knowledge anyway)

Tadaaa! Hope it helps!

I've been looking around this problem and has come across this post so thought that my answer would benefit someone in the future.

Have a lovely day!

Marek

Marek Štefanec
  • 386
  • 1
  • 3
  • 10
5

If you came to this question while trying to find the issue because response.json() throws "Unexpected token at position..." and you can't find the issue with the JSON, then you can try this, basically getting the text and then parsing it

fetch(URL)
  .then(async (response) => {
    if (!response.ok) {
      const text = await response.text()
      throw new Error(text)
    }
    // Here first we convert the body to text
    const text = await response.text()
    // You can add a console.log(text), to see the response
    // Return the JSON
    return JSON.parse(text)
  })
  .catch((error) => console.log('Error:', error))
  .then((response) => console.log(response))
Computer's Guy
  • 5,122
  • 8
  • 54
  • 74
2

I think you need to do something like this

export function create_user(user,userInfoParams={}) {

  return function (dispatch) {
    dispatch(update_user(user));

    return fetch(deafaultUrl + '/v1/users/',
        {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            method: "POST",
            body: JSON.stringify(userInfoParams)
        })
        .then(function(response) {
            console.log(response);
            console.log(response.body);
            console.log(response.message);
            console.log(response.errors);
            console.log(response.json());
            return response.json();
        })
        .then(function(object){
          if (object.errors) {
            dispatch(update_errors(response));
            throw new Error(object.errors);
          } else {
            console.log("succeed json re");
            dispatch(update_user(json));
          }
        })
        .catch(function(error){
          this.setState({ error })
        })
       }
     }
Neeraj Kumar
  • 6,045
  • 2
  • 31
  • 23
1

You can access the error message with this way:

return fetch(deafaultUrl + '/v1/users/',
            {
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                method: "POST",
                body: JSON.stringify(userInfoParams)
            })
            .then(function(response) {
                console.log(response);
                console.log(response.body);
                console.log(response.message);
                console.log(response.errors);
                console.log(response.json());
                dispatch(update_errors(response));

                if (response.status >= 400) {
                    throw new Error("Bad response from server");
                }

            })
            .then(function(json){
                console.log("succeed json re");
                // We can dispatch many times!
                // Here, we update the app state with the results of the API call.

                dispatch(update_user(json));

            })
            // here's the way to access the error message
            .catch(function(error) {
              console.log(error.response.data.message)
            })

;

0

The best choice is not to catch the error in the fetch because this will be useless:

Just in your api put a response with not code error

static GetInvoicesAllData = async (req,res) =>
{
try{
  let pool =  await new Connection().GetConnection()
 let invoiceRepository =   new InvoiceRepository(pool);
 let result =  await invoiceRepository.GetInvoicesAllData();
 res.json(result.recordset);
 }catch(error){
     res.send(error);
 }
} 

Then you just catch the error like this to show the message in front end.

fetch(process.env.REACT_APP_NodeAPI+'/Invoices/AllData')
        .then(respuesta=>respuesta.json())
        .then((datosRespuesta)=>{
        
            if(datosRespuesta.originalError== undefined)
            {
                this.setState({datosCargados:true, facturas:datosRespuesta})
            }
            else{ alert("Error: " + datosRespuesta.originalError.info.message ) }
})
        

With this you will get what you want.

0

With asynchronous routes:

From api, return your error message like this :

        return res.status(400).json({ message: error.message});

On client side:

catch(error) {

  if(error?.response) {
    const jsonError = await error.response.json(); // must await for response

    const message = jsonError.message;

    console.error(message)

  }
}
Sylvain L
  • 166
  • 1
  • 4
0

Alternative way to do this with async/await and try/catch:

const myAPICall = async (data) => {

const options = {
  method: "POST",
  headers: {
  //your headers
  },
  body: JSON.stringify(data)
}

try {
  const response = await fetch('http://localhost:3001/api/', options)
  return await response.json()
}catch(error){
  console.error(error)
}

-3

You variables coming back are not in response.body or response.message.

You need to check for the errors attribute on the response object.

if(response.errors) {
    console.error(response.errors)
}

Check here https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

You should actually be returning an error response code from the server and use the .catch() function of the fetch API

ryan0319
  • 412
  • 2
  • 9
-3

First you need to call json method on your response.

An example:

fetch(`${API_URL}`, {
        method: 'post',
        headers: {
           'Accept': 'application/json',
           'Content-Type': 'application/json'
        },
        body: JSON.stringify(userInfoParams)
    })
    .then((response) => response.json())
    .then((response) => console.log(response)) 
    .catch((err) => {
        console.log("error", err) 
    });

Let me know the console log if it didn't work for you.

Dhyey
  • 4,275
  • 3
  • 26
  • 33
  • Thanks guys but finally i found the way inside this http://stackoverflow.com/questions/40418981/how-can-i-get-errors-value-inside-promise-object-which-return-from-fetch-api/40419846#40419846 – Varis Darasirikul Nov 04 '16 at 17:00
  • @VarisDarasirikul If your approach is different from the other answers, consider answering your own question to provide a helpful resource for other users reading this question. Otherwise you can upvote or accept whatever answers you think answer your question. – Nick McCurdy Sep 22 '21 at 10:33