0

I want to build a central ajax handler for all of my requests, inside my React app. It has nothing to do with Redux or anything similar. It's just a "central junction" for performing ajax requests, from React components.

The thing is, that i want to be able to intercept certain errors, to avoid repeating it in every ajax call. Eventually, i want to call then() again, from the component that actually initiated the request

This is what i have done:

import axios from 'axios';
import handleError from './error';

export default (endPoint, method = 'get', data) => {

return axios({
    url: window.BASE_API + endPoint,
    method,
    data
}).then(({ data }) => {
    if (data.status !== 'ok') {
        handleError(data.errorMessage);
    }
}).catch((error) => {
    handleError(error);
})

}

In the react component:

import ajax from '../../services/ajax';

componentDidMount() {
ajax('/content')
  .then(({ data }) => {
    this.setState(() => ({ pages: data.content }))
  })
 }

This results in an error: Uncaught (in promise) TypeError: Cannot read property 'data' of undefined

How can i handle the promise twice?

EDIT: this is how my response object looks like:

 {content: Array(18), status: "ok", errorMessage: null, metaData: null}
i.brod
  • 3,993
  • 11
  • 38
  • 74
  • Return the data in your first then and it should work properly. – Axnyff Apr 26 '18 at 13:50
  • It doesn't....tells me "Uncaught TypeError: Cannot read property 'content' of undefined". – i.brod Apr 26 '18 at 14:15
  • Well `handleError` doesn't return anything. What do you want to do with the promise after "intercepting" the error? – Bergi Apr 26 '18 at 14:20
  • Well in my case, "handleError" isn't even being called, because the "status" of my request is "ok". After intercepting the error i do not want to with it anything actually, but if there's no error - i want to be able to handle it from the component it self(which basically means, calling then() twice on the same promise..) – i.brod Apr 26 '18 at 14:27
  • If that's your response object, then you probably want `.then(data => {...})`, not `.then(({data}) => {...})`. – Roamer-1888 Apr 26 '18 at 16:00
  • Also be sure (i) to pass `Error` (or `RangeError` etc) to `handleError` in all circumstances, and (ii) to allow `handleError` to perform error recovery by returning its return value; eg. `if (data.status !== 'ok') { return handleError(new Error(data.errorMessage)); }`. That way, `handleError()` is free to throw, to return a value, or to return a Promise; all will propagate correctly. – Roamer-1888 Apr 26 '18 at 16:01

2 Answers2

2

Updated to get rid of the explicit promise construction antipattern. Thanks Roamer!

It might be good in this case to have your API call return a promise itself. Something like this:

const apiCall = (params) => axios(params).then({ data } => { // Note here that you are extracting the property "data" from whatever the axios call returns.
  if (data.status !== 'ok') {
    throw new Error(data.errorMessage) // Rejects the promise
  } else {
    return data // Resolve the data
  }
)}

Then you would use it like so:

apiCall(params)
   // Since you resolved the raw data, no need to use object extraction unless the property you're trying to get out is `data.data`
  .then(data => this.setState({pages: data.content}))
  .catch(e => handleError(e))

That's how you could effectively make a custom promise that handles all the error cases you're talking about. There are a couple things that could be happening, but I think one of them could be that you first extract {data} from the axios response, then consume the promise result without resolving new data. Then in your .then() call, you extract {data} again there (and as the error says, it is undefined). If you wrap the whole call in a new promise, you can handle it (I think) exactly how you want.

Noah Allen
  • 699
  • 5
  • 14
  • 1
    Ok, this seems to work, thank you. the problem was indeed with me extracting the "data" property from the axios object. Once i changed the then(({data})) to then((data)) in my component - it works. – i.brod Apr 26 '18 at 14:49
  • Feel free to mark as solution if this worked for you :) I'm glad it worked! – Noah Allen Apr 26 '18 at 15:09
  • 1
    Yes ... but you have unnecessarily introduced the [explicit promise construction antipattern](https://stackoverflow.com/questions/23803743/). – Roamer-1888 Apr 26 '18 at 15:54
  • Thanks! Just fixed that. Having learned about that a while ago, I never updated my answers which used it. – Noah Allen Jul 05 '19 at 21:58
1

Try this:

  return axios({
    url: window.BASE_API + endPoint,
    method,
    data
   }).then(({ data }) => {
     if (data.status !== "ok") {
        handleError(data.errorMessage);
     } else {
        return Promise.resolve(data);
     }
  }).catch((error) => {
  handleError(error);
 })
 }

You should also change this:

componentDidMount() {
 ajax('/content')
   .then(({ content }) => {
   this.setState(() => ({ pages: content }))
 })
}
  • Still doesn't work. "Uncaught TypeError: Cannot read property 'content' of undefined". – i.brod Apr 26 '18 at 14:25
  • you were using data.status !== "ok" it should be status !== 200 look at https://github.com/axios/axios#response-schema now it should work – Antonio Pangallo Apr 26 '18 at 14:32
  • Lol no..i probably should've clarified it, to prevent this confusion: the "status" is just a property on my response object. This is just how i've built my backend API. it doesn't refer to the http status code, it's just my own custom handling of "errors", like "no results found", or something like that. So the status might be "fail", even if the http status code is 200, they are just unrelated – i.brod Apr 26 '18 at 14:39
  • 1
    I've added the response object at the bottom of my post – i.brod Apr 26 '18 at 14:44
  • may you also try to change componentDidMount()? – Antonio Pangallo Apr 26 '18 at 14:48
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/169876/discussion-between-antonio-pangallo-and-sheff2k1). – Antonio Pangallo Apr 26 '18 at 14:49