reduxjs / redux-toolkit

The official, opinionated, batteries-included toolset for efficient Redux development
https://redux-toolkit.js.org
MIT License
10.74k stars 1.18k forks source link

How can I get the url of the API in the error middleware? #3618

Closed nouman91 closed 1 year ago

nouman91 commented 1 year ago

I am using a pretty standard implementation of RTK query from the docs nothing too fancy. I have a situation where I would want to know the API URL that failed in the error middleware.

I checked in the docs and few other sites but could not find it. Here is my error middleware:

`export const errorMiddleware: Middleware = (store) => (next) => (action) => { /**Rejection from interal redux cache we want to ignore it since it is not a rejection:

nouman91 commented 1 year ago

I also want to get the status code as well.

EskiMojo14 commented 1 year ago

assuming you're using fetchBaseQuery, request and response objects are attached to action.meta.baseQueryMeta - this presumably will have the URL and status information you want.

proving this in typescript will probably be a little harder, without some type assertions.

nouman91 commented 1 year ago

@EskiMojo14 Thank you for your response thing is I am not seeing this information. Here is the full code:

Api Slice

export const someApiSlice = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: getExternalUrlPath(),
    prepareHeaders: prepareHeaders,
  }),
  endpoints: () => ({}),
  reducerPath: 'sites',
}) 

Api Queries

export const someApi = someApiSlice.injectEndpoints({
  endpoints: (builder) => ({
    getSites: builder.query<InfTO, string[]>({
      query: () => ({
        url: 'some-url',
      }),
      transformResponse: (res: { info: InfoTO[] }, _, args) => {
        const result: InfoTO = res.info.reduce((prev, curr) => {
          const { site_id, ...rest } = curr;
          if (args.indexOf(site_id) >= 0) {
            return { ...prev, [site_id]: { ...rest } };
          }
          return prev;
        }, {});

        return result;
      },
    }),
  }),
}); 

Error Middleware

export const errorMiddleware: Middleware = (store) => (next) => (action) => {
   */
  const conditionError = 'ConditionError';
  /**If we got actual rejection from the APIs than show a toast */
  if (
    isRejectedWithValue(action) ||
    (action.type.endsWith('/rejected') && action.error.name !== conditionError)
  ) {
    console.log(action);
    console.log(store);
    store.dispatch(showToast({ message: getErrorMessage(action.error), type: 'error' }));
  }
  return next(action);
};

Console Output

{
    "type": "**/**/**",
    "meta": {
        "arg": [
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "f3e3d7af-97cd-4a06-93d2-aae9cee5b276",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "f3e3d7af-97cd-4a06-93d2-aae9cee5b276",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "993f79f8-fead-42cd-9e7d-70d848e2e1a9",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "37b569fc-c67a-496d-8d65-88fc1adb9ace",
            "37b569fc-c67a-496d-8d65-88fc1adb9ace",
            "f3e3d7af-97cd-4a06-93d2-aae9cee5b276",
            "37b569fc-c67a-496d-8d65-88fc1adb9ace"
        ],
        "requestId": "BB6CgUBQ-NLfAiLhlEmrE",
        "rejectedWithValue": false,
        "requestStatus": "rejected",
        "aborted": false,
        "condition": false
    },
    "error": {
        "name": "Error",
        "message": "Request failed with status code 404",
        "stack": "Error: Request failed with status code 404\n    at createError (http://localhost:6014/vendors~main.iframe.bundle.js:201591:15)\n    at settle (http://localhost:6014/vendors~main.iframe.bundle.js:201891:12)\n    at XMLHttpRequest.onloadend (http://localhost:6014/vendors~main.iframe.bundle.js:200903:7)"
    }
}
EskiMojo14 commented 1 year ago

that doesn't look like a RTKQ action, the type would be ${api.reducerPath}/executeQuery/rejected and the arg would have { type: 'query' } . (also that looks like it's using XMLHttpRequest, which fetchBaseQuery definitely doesn't do)

do you have any other createAsyncThunk calls that might be having that error instead?

nouman91 commented 1 year ago

@EskiMojo14 Yes, the type is what you mention and I just quickly redacted it to put the comment faster. Rest is the complete setup of the APIs that I had. Can you point me to where the problem is by looking into the code? I formatted my old comment to make it easier to read.

EskiMojo14 commented 1 year ago

Are you sure? The argument doesn't look right - it would be something like { type: 'query', originalArgs: [...ids], endpointName: 'getSites', queryCacheKey: 'getSites([...ids])' }

Would you be able to record a Replay or create a reproduction in CodeSandbox?

nouman91 commented 1 year ago

Update: Upon further investigation, I found out when API returns 404 the error object does not get the full information like for other error types:

404 Error Object

{
    "type": "*/*/*",
    "meta": {
        "arg": [
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "f3e3d7af-97cd-4a06-93d2-aae9cee5b276",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "f3e3d7af-97cd-4a06-93d2-aae9cee5b276",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "993f79f8-fead-42cd-9e7d-70d848e2e1a9",
            "4968f80d-70d9-4222-90e6-a42778bc49c4",
            "37b569fc-c67a-496d-8d65-88fc1adb9ace",
            "37b569fc-c67a-496d-8d65-88fc1adb9ace",
            "f3e3d7af-97cd-4a06-93d2-aae9cee5b276",
            "37b569fc-c67a-496d-8d65-88fc1adb9ace"
        ],
        "requestId": "URqM0fT50Tl-xqsDFZlyB",
        "rejectedWithValue": false,
        "requestStatus": "rejected",
        "aborted": false,
        "condition": false
    },
    "error": {
        "name": "Error",
        "message": "Request failed with status code 404",
        "stack": "Error: Request failed with status code 404\n    at createError (http://localhost:6014/vendors~main.iframe.bundle.js:202592:15)\n    at settle (http://localhost:6014/vendors~main.iframe.bundle.js:202892:12)\n    at XMLHttpRequest.onloadend (http://localhost:6014/vendors~main.iframe.bundle.js:201904:7)"
    }
}

422 Error Object

{
    "type": "*/*/*",
    "payload": {
        "status": 422,
        "data": {
            "error_code": 10202,
            "error_message": "Invalid ids",
            "module_name": ""
        }
    },
    "meta": {
        "baseQueryMeta": {
            "request": {},
            "response": {}
        },
        "RTK_autoBatch": true,
        "arg": {
            "type": "query",
            "subscribe": true,
            "subscriptionOptions": {
                "pollingInterval": 0
            },
            "endpointName": "get",
            "originalArgs": [
                "14bfa34d-e087-4147-b73a-18417e290e1b",
                "a35184c6-5ed0-4c95-a20e-93bba7024de3"
            ],
            "queryCacheKey": "get**([\"14bfa34d-e087-4147-b73a-18417e290e1b\",\"a35184c6-5ed0-4c95-a20e-93bba7024de3\"])"
        },
        "requestId": "LdzC9JPKVTvqo44nq6MOo",
        "rejectedWithValue": true,
        "requestStatus": "rejected",
        "aborted": false,
        "condition": false
    },
    "error": {
        "message": "Rejected"
    }
}

I am however still not seeing the URL for the request though.

EskiMojo14 commented 1 year ago

it'll be action.meta.baseQueryMeta.request.url, Request instances just aren't serializeable.

nouman91 commented 1 year ago

@EskiMojo14 I see it there, but any idea why 404 is not behaving correctly?

EskiMojo14 commented 1 year ago

none - like i said, there's nothing in that action that suggests it's actually an action from RTKQ, and the stack mentioning XMLHttpRequest is especially odd for something supposedly using fetch.

nouman91 commented 1 year ago

Thank you @EskiMojo14 for being so responsive and helpful, I appreciate it.

In my use case, I want to get all the API errors in error middleware and display them to the user. So far I have been encountering a few problems regarding accessing the error message. A few of the APIs have error messages in the error. message field but few have only 'rejected' in error. message field. I am not sure why is that and which field should I be using to access the error message.

The error object in question:

{
    "type": "api/executeQuery/rejected",
    "payload": {
        "status": 422,
        "data": {
            "error_code": 10202,
            "error_message": "Invalid ",
            "module_name": "**"
        }
    },
    "meta": {
        "baseQueryMeta": {
            "request": {},
            "response": {}
        },
        "RTK_autoBatch": true,
        "arg": {
            "type": "query",
            "subscribe": true,
            "subscriptionOptions": {
                "pollingInterval": 0
            },
            "endpointName": "**",
            "originalArgs": [
                "14bfa34d-e087-4147-b73a-18417e290e1b",
                "a35184c6-5ed0-4c95-a20e-93bba7024de3"
            ],
            "queryCacheKey": "**([\"14bfa34d-e087-4147-b73a-18417e290e1b\",\"a35184c6-5ed0-4c95-a20e-93bba7024de3\"])"
        },
        "requestId": "9E6Tp-e6iIQKShfmLp0Ye",
        "rejectedWithValue": true,
        "requestStatus": "rejected",
        "aborted": false,
        "condition": false
    },
    "error": {
        "message": "Rejected"
    }
}
EskiMojo14 commented 1 year ago

For errors that have been rejectedWithValue, the error will be located at action.payload. For RTKQ, this is used for any "handled" errors (i.e. the base query has returned { error } because the request failed). This will be a FetchBaseQueryError in the case of fetchBaseQuery.

The only time with RTKQ that you'll get errors not rejectedWithValue is for uncaught errors in the process of executing the endpoint - this usually indicates that something went significantly wrong with one of your endpoint settings - or for condition rejections (which in the context of RTKQ means you're subscribing to a cached result and don't need to fetch it).

Therefore, you can safely use isRejectedWithValue to match actions (along with checking the meta looks like a RTKQ action) and action.payload will be the actual error value.

nouman91 commented 1 year ago

Ok, Make sense. Let me see my setup and configure error handling accordingly to what you have shared here.

Do you think 404's error handling issue is a legit issue and needs to be looked at? For the 404 case, I don't get the necessary information. Let me know if you need any details on it.

EskiMojo14 commented 1 year ago

If you can provide enough code to replicate it, or record it happening like i asked, then yes it might be an issue needing looking into.

Until then, I'm not convinced it's RTKQ related as it's entirely the wrong shape (and removing the action type doesn't fill me with confidence either).

nouman91 commented 1 year ago

ok, I will try to record it and post the results here.

EskiMojo14 commented 1 year ago

Here's a codesandbox, showing a 404 being correctly caught.

You can also see that action.meta.baseQueryMeta.request.url is the url, and the status is at action.meta.baseQueryMeta.response.status as well as action.payload.status.