INTRO :
I have an app in reactjs, using redux, redux-observable and axios-observable.
I face an issue with HTTP error handling.
Let's take the following Epic :
const loginRequest = (action$, state$) => action$.pipe(
ofType(UsersActions.loginRequest),
switchMap((action: {payload:{email: string, password: string}}) =>
HttpService.PostAsync<any>('token-auth', action.payload).pipe(
map(response => {
// blabla
}),
catchError((error: string) => {
// blabla
})
)
)
);
the HttpService looks like this
public static PostAsync<T>(targetApi: string, data: any, basePath?: string): AxiosObservable<T> {
return Axios.post(this.getBaseUrl(targetApi, basePath), data);
}
So this works correctly, if the post request fail, I get into the catchError, if it doesn't i go into the normal map.
PROBLEM :
I would like to intercept the response, in order to add a global app error handling, I setup the following function :
Axios.interceptors.response.use(response => {
console.log(`[response] --> ${response.status}`)
return response;
}, error => {
console.log(`[error] --> ${error}`)
return throwError(error);
})
I can now see the log, error, or response depending on the HTTP request result. BUT, I will ALWAYS go into the map, and never into the catchError of my Epic.
QUESTION :
How can I interpect the error, but still throw an error to the redux-observable epic ?
throwError only works within RX chain, try use native JS throw
Axios.interceptors.response.use(response => {
console.log(`[response] --> ${response.status}`)
return response;
}, error => {
console.log(`[error] --> ${error}`)
throw error
})
Related
Well, I'm a little dumpy. I will try to explain my problem as clearly as possible.
I use Apollo client to do my GraphQL queries. I also use NextJS.
I have a page that needs to be rendered on the server side for SEO reasons.
So I have a getProductFromSlug function that allows me to execute my request.
export const getProductFromSlug = async (slug: string) => {
try {
const { data, error } = await apolloClient.query<{
product: Product
}>({
query: GET_PRODUCT_BY_SLUG_QUERY,
variables: {
slug,
},
})
if (error) {
return { errors: [error.message] }
}
if (!('product' in data) || data.product === null) {
return { errors: ['Product with specified url not found'] }
}
return {
data,
}
} catch (error) {
// #ts-ignore
const formattedErrors: ApolloError = isApolloError(error)
? error.graphQLErrors.map((error) => error.message)
: [`Unhandled error : ${error}`]
return {
errors: formattedErrors,
}
}
}
Here's getServerSideProps to pass data to page
export const getServerSideProps = async (
context: GetServerSidePropsContext
) => {
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore
const requestData = await getProductFromSlug(context.params.slug as string)
return 'errors' in requestData
? { notFound: true, props: requestData }
: { props: requestData }
}
The problem is that when I have a HTTP code 500 from the endpoint, the SSR is crashing and on Vercel, it's causing a serverless crash error.
Error: Response not successful: Received status code 500
This error happened while generating the page. Any console logs will be displayed in the terminal window
If needed, here's my entry point (_app.tsx):
function MyApp(props: AppProps) {
return (
<ApolloProvider client={apolloClient}>
<RecoilRoot>
<RecoilNexus />
<AuthenticationFromStorage />
<Layout>
<props.Component {...props.pageProps} />
</Layout>
</RecoilRoot>
</ApolloProvider>
)
}
You can see my Apollo Client here : https://gist.github.com/SirMishaa/d67e7229307b77b43a0b594d0c9e6943
Stack trace of yarn run dev (next dev -p 3005) :
ServerError: Response not successful: Received status code 500
at Object.throwServerError (C:\Users\misha\Documents\dev\rekk-next\node_modules\#apollo\client\link\utils\utils.cjs:45:17)
at C:\Users\misha\Documents\dev\rekk-next\node_modules\#apollo\client\link\http\http.cjs:31:19
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
error - uncaughtException: ServerError: Response not successful: Received status code 500
error Command failed with exit code 1.
NOTE :
After some try with console.log in try and catch scope, it shows nothing in the Next SSR console, so the internal error of Apollo is not caught for some reason.
I appreciate your help, thank you!
UPDATE
The issue was that PusherLink was not continuing the execution in the chain when there was an error. Adding the error and complete handlers solved the problem.
forward(operation).subscribe({
next: (data) => {
...
this.subscribeToChannel(subscriptionChannel, observer)
},
// these two were missing
error: (error) => observer.error(error),
complete: () => observer.complete(),
})
JIC, I also added a missing condition the code from the other link taken as a reference has
subscribeObservable.subscribe = (observerOrNext, onError, onComplete) => {
if (typeof(observerOrNext) == "function") {
prevSubscribe(observerOrNext, onError, onComplete)
} else {
prevSubscribe(observerOrNext)
}
OLD ANSWER
Your code inside the catch block could be throwing an error, and that's what is breaking. I'm unsure about this condition Array.isArray(apolloError), maybe you meant Array.isArray(apolloError.graphQLErrors)
You could try the isApolloError utility to be a little more clear and get some hints from TS types.
import { isApolloError } from '#apollo/client';
...
const formattedErrors = isApolloError(e)
? e.graphQLErrors.map(error => error.message)
: [`Unhandled error : ${e}`];
return {
errors: formattedErrors,
};
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?
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.
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.
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.
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()
})
I want to show some errors that comes from graphql server to user.
Have some component with callback that use some mutation
onSave() => {
this.props.mutate({
mutation: CHANGE_ORDER_MUTATION,
variables: {
ids,
},
}).catch((e) => {
console.log(e.graphQLErrors) <--- e.graphQLErrors is always empty array = []
})
}
While I'm able to see the graphQLErrors error with apollo-link-error link.
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors) <--- errors from server
});
Migration to apollo-server instead of express-graphql solve the problem.
Or you can access errors by e.networkError.result.errors
As I understand it, the 'catch' will catch errors if your server returns an error code, but not if it returns your errors in the response, but with a success code. In that case, you just need to get your errors out of the response (keeping your catch too in case the server responds with an error code):
this.props.mutate({
mutation: CHANGE_ORDER_MUTATION,
variables: {
ids,
},
})
.then((response) => {
if (response.errors) {
// do something with the errors
} else {
// there wasn't an error
}
})
.catch((e) => {
console.log(e.graphQLErrors) <--- e.graphQLErrors is always empty array = []
})
Alternatively - if you use the graphql() option from react-apollo, you can specify an onError handler:
export default graphql(CHANGE_ORDER_MUTATION, {
options: (props) => ({
onCompleted: props.onCompleted,
onError: props.onError
}),
})(MyComponent);
references:
https://github.com/apollographql/react-apollo/issues/1828
Apollo client mutation error handling
What I'm you doing?:
I would like to thrown an error on apollo-server and process it on client.
Note: I'm using apollo-link-error middleware on apollo-client.
Server:
import { UserInputError } from "apollo-server";
Mutation: {
someMutation : {
try {
// here is some code which failed
} catch (error) {
// base Error class has message property by default
// response just hold some additional informations
throw new UserInputError(error.message, { response });
}
}
}
Client:
simplified implementation of my mutation on client
<Mutation
mutation={CREATE_ORDER}
>
{(createOrder, { loading, error }) => (
....
try {
await createOrder({ variables: {...}});
} catch (createOrderError) {
// here I get Cannot read property 'data' of undefined
console.log(createOrderError);
}
)}
</Mutation>
I receive following error on client (in catch clause in the code above):
TypeError: Cannot read property 'data' of undefined
at Mutation._this.onMutationCompleted (react-apollo.browser.umd.js:631)
at react-apollo.browser.umd.js:586
This error looks like problem with httpLink.
Response: (from network tab in chrome dev tools)
From graphql spec :
If an error was encountered during the execution that prevented a
valid response, the data entry in the response should be null.
So I assume that my response from server is valid. The data object should be null.
What do I expect to happen?:
I would like to access to response from apollo server How could I achieve this?
Thanks for help!
Things to check before accessing the data returned from query or mutation
If loading -> return some loader component
If error is present -> display some error component
If not any of above two conditions matched then for sure you have the data.
Apart from that you need to have
1.On Apollo Client "errorPolicy"
const client = new ApolloClient({
defaultOptions: {
watchQuery: {
errorPolicy: 'all'
},
query: {
errorPolicy: 'all'
},
mutate: {
errorPolicy: 'all'
}
},
link,
cache,
connectToDevTools: true,
})
2.For customising error sent from server -
You can use formatError
const server = new ApolloServer({
...root,
resolverValidationOptions: {
requireResolversForResolveType: false,
},
formatError, <---------- send custom error
formatResponse: (response, query) => formatResponse({ response, query }),
dataSources,
context: async ({ req, res }) => {
const user = req.user;
return { user, req, res };
}
});
e.g
const formatError = (error) => {
const { extensions } = error;
logger.error(error);
const exception = extensions.exception ? extensions.exception : {};
logger.error('\nStackTrace');
logger.error(exception.stacktrace);
exception.stacktrace = null;
const extractedError = extractErrorFromExtention(extensions);
return extractedError || { message: error.message, code: extensions.code, exception };
};
I am getting above error while fetching some data from the API. Following is the code of the action creator where I am trying GET the data:
import { FETCH_USER } from './types';
import axios from 'axios';
export const fetchUser = () => async dispatch => {
console.log('fetchUser');
const res= await axios.get('/api/current_user');
dispatch({ type: FETCH_USER, payload: res });
};
Also when I am debugging in the code editor, console is giving me below error:
SyntaxError: Unexpected token import
Generally this error comes when the url/location provided inside GET method is not correct.
So check the url/location again and correct it.
So most probably there is some mistake here : '/api/current_user'
In my case it was a minor syntax error (a misplacement of my brackets).
My code looked like this:
axiosget("/api-url").then(
(response) => {
doSomething();
}), () => {
doError();
}
);
instead of this:
axiosget("/api-url").then(
(response) => {
doSomething();
}, () => {
doError();
});
);
If you get this error message it means that your error handling-code isn't there or that it could not be reached, therefore the promise was unhandled (since the catch-part of your wasn't properly setup).
If you get this message always doublecheck your syntax!
If your are using laravel for API and vue/Nuxtjs for frontend and axios for send data to API....
This type of errors can be faced for laravel validation error sending not in correct way using try{} catch(){} block or receiving errors by axios not in correct way to using try() catch(){} block.
Here, try catch block using for error handling.
If your API routes called the public function its name "register()", so your function inside your controller have to like following...(I am using laravel-8 for API)
public function register(Request $request) {
try {
$fields = $request->validate([
'name' => 'required|string',
'email' => 'required|string|email|unique:users',
'password' => 'required|string|confirmed',
]);
$user = User::create([
'name' => $fields['name'],
'email' => $fields['email'],
'password' => bcrypt($fields['password'])
]);
$token = $user->createToken('myapptoken')->plainTextToken;
$response = [
'user' => $user,
'token' => $token,
];
return response()->json($response, 200);
} catch(ValidationException $e) {
return response()->json([
'status' => 'error',
'msg' => 'error',
'errors' => $e->errors()
], 422);
}
}
and Frontend Nuxt or vue methods name is "registerNewUser()" so, codes can be following for error handling...
async registerNewUser() {
try {
let data = {
name : this.name.trim(),
email : this.email.trim(),
password : this.password.trim(),
password_confirmation : this.password_confirmation.trim(),
};
let headers = {
headers: {
'Accept': 'application/json',
}
};
await this.$axios
.post("/api/register", data, headers)
.then((response) => {
console.log("response", response);
if(response) {
// console.log(response)
} else {
}
});
} catch(err) {
// here we are receiving validation errors
console.log("Err == ", err.response);
console.log(err.response.data.errors);
}
}
You are receiving response inside axios then block or receive error inside catch block using err.response
Here,
let data = {
name : this.name.trim(),
email : this.email.trim(),
password : this.password.trim(),
password_confirmation : this.password_confirmation.trim(),
};
Given codes is for data of Nuxtjs or vuejs. If not know that you can using like following data or any other data...
let data = {
name : 'Kallol',
email : 'kallolray94#gmail.com',
password : '123456',
password_confirmation : '123456',
};