is somebody able to explain, why my react app + apollo behaves like this when I try to use mutation which returns error?
GraphQL mutation returns this (response code is 200): {"errors":[{"error":{"result":"identity.not-found","error":"authentication-failed","statusCode":401}}],"data":{"login":null}}
My mutation looks like this:
export const LOGIN_MUTATION = gql`
mutation($input: LoginInput!) {
login(input: $input) {
token
}
}
`;
called:
const handleSignIn = () => {
loginMutation({
variables: {
input: {
clientId: config.clientId,
username: userName,
password: password,
clientSecret: config.clientSecret
}
}
});
};
It behaves for awhile like expected (my own custom error component is rendered - {error && <div>error</div>}), but then it throws this unhandled rejection.
If I add catch callback to mutation call, it works as expected.
However, I did not find anywhere in apollo docs any mentions about the need to always catch GraphQL errors such way. This should be sufficient, if I understand it correctly: const [loginMutation, {data, loading, error}] = useMutation(LOGIN_MUTATION);
Is this behaviour correct or do I miss something?
Versions:
"#apollo/react-hooks": "^3.1.3"
"apollo-boost": "^0.4.7"
"graphql": "^14.5.8"
The mutate function returns a Promise that, by default, will be rejected if your response includes an errors object or if a network error occurs. If you change the errorPolicy to ignore or all, the presence of errors in your response won't cause the Promise to reject, but a network error still will.
The only way to avoid this behavior is to provide an error handler through the onError parameter. If onError is provided, the Promise returned by mutate will always resolve and any relevant errors (depending on your errorPolicy) will be passed to onError instead.
Additional details on error handling in Apollo can be found here. Additional details on unhandled Promise rejections can be found here.
Related
I want to be able to provide a custom error (properly typed) to react-query's useQuery hook. The reason for this custom error is to be able to provide to it the status code of the response in case it fails, so I can determine whether or not to use the useErrorBoundary option based on that status.
I've read that it's not a very good idea to provide generics to useQuery like so:
const { data, isLoading } = useQuery<DataType, CustomErrorType>(
['example'],
() => getData(),
{
useErrorBoundary: (error) => error.status >= 500
}
)
And that it's a better idea to let react-query infer the types by typing the queryFn instead. If I were using axios this wouldn't be an issue since it provides some utilities and types for errors, but I'm working with fetch.
So, what would be the best way to get a correctly typed custom error in this case?
the Error type can be inferred from all "places", so it works if you provide a type annotation to useErrorBoundary:
const { data, isLoading } = useQuery(
['example'],
() => getData(),
{
useErrorBoundary: (error: CustomErrorType) => error.status >= 500
}
)
this will make the type of Data still be inferred from the queryFn (and eliminate all other problems that come from passing generics manually), and error will still be CustomErrorType
TypeScript playground here
Here again for another question which I can't seem to figure out.
I am using Apollo Client (with react) to communicate with my GraphQL server.
I get an error when I try to perform a mutation and I don't know where the issue is coming from because other mutations work fine.
This is the mutation I'm having an issue with:
const DELETE_USER_MUTATION = gql`
mutation deleteUser($id: ID!) {
deleteUser(id: $id) {
id
}
}
`;
The mutation works fine when I input it in the GraphQL playground...
This is how I call the mutation (triggered by a button click):
<button
onClick={() =>
deleteUser({
variables: {
id: user.id,
},
})
}>
Note: user.id is obtained by performing a query and it is valid.
Finally, this is how the deleteUser function is defined (using the useMutation hook)
const [deleteUser, { error }] = useMutation(DELETE_USER_MUTATION, {
refetchQueries: [{ query: GET_USERS_QUERY }],
});
On a side note, how can one debug such errors? On the console I get an error 400, which I read means that it's probably an error with the mutation itself. But I don't get more information about the error... I tried to catch it but to no avail.
Thank you!
I was able to solve the issue and I can't believe I didn't realize the problem sooner.
After checking the type of user.id, I realized it was a string instead of an integer. Converting it to a an Int worked.
However I'm having some cache issues now... Time for more research
I have been trying to get my head around this problem but haven't found a strong answer to it. I am trying to execute a login mutation using the useMutation hook.
TLDR; I want to know what exactly is the difference between the onError passed in options and error given to me by the useMutation
Here's my code snippet
const [login, { data, loading, error }] = useMutation(LOGIN_QUERY, {
variables: {
email,
password
},
onError(err) {
console.log(err);
},
});
On the server-side, I have a preset/hardcoded email used for login and I am not using Apollo or any other client. In the resolver of this Login Mutation, I simply throw an error if the email is not same using
throw new Error('Invalid Email');
Now I want to handle this error on the client-side (React). But my concern is that if I use the 'error' returned from the useMutation hook and try to show the error in this way
render() {
...
{error && <div> Error occurred </div>}
...
}
the error is updated in the UI but then immediately React shows me a screen with:
Unhandled Rejection (Error): Graphql error: My-custom-error-message
But, if I use onError passed in options to useMutate function, then it doesn't show me this screen and I can do whatever I want with the error.
I want to know what exactly is the difference between the onError passed in options and error given to me by the useMutation and why does React show me that error screen when onError is not used.
Thanks!
Apollo exposes two kinds of errors through its API: GraphQL errors, which are returned as part of the response as errors, alongside data, and network errors which occur when a request fails. A network error will occur when a server can't be reached or if the response status is anything other than 200 -- queries that have errors in the response can still have a status of 200. But an invalid query, for example, will result in a 400 status and a network error in Apollo Client.
Apollo Client actually provides four different ways to handle mutation errors:
1.) Calling the mutate function returned by the hook returns a Promise. If the request is successful, the Promise will resolve to a response object that includes the data returned by the server. If the request fails, the Promise will reject with the error. This is why you see an "Unhandled Rejection" message in the console -- you need to handle the rejected Promise.
login()
.then(({ data }) => {
// you can do something with the response here
})
.catch(e => {
// you can do something with the error here
})
or with async/await syntax:
try {
const { data } = await login()
} catch (e) {
// do something with the error here
}
By default, the Promise will reject on either GraphQL errors or network errors. By setting the errorPolicy to ignore or all, though, the Promise will only reject on network errors. In this case, the GraphQL errors will still be accessible through the response object, but the Promise will resolve.
2.) The only exception to the above occurs when you provide an onError function. In this case, the Promise will always resolve instead of rejecting, but if an error occurs, onError will be called with the resulting error. The errorPolicy you set applies here too -- onError will always be called for network errors but will only be called with GraphQL errors when using the default errorPolicy of none. Using onError is equivalent to catching the rejected Promise -- it just moves the error handler from the call site of the mutate function to the call site of the hook.
3.) In addition to the mutate function, the useMutation hook also returns a result object. This object also exposes any errors encountered when running the mutation. Unlike the error handler functions we wrote above, this error object represents application state. Both the error and data objects exposed this way exist as a convenience. They are equivalent to doing this:
const [mutate] = useMutation(YOUR_MUTATION)
const [data, setData] = useState()
const [error, setError] = useState()
const handleClick = async () => {
try {
const { data } = await mutate()
setData(data)
catch (e) {
setError(e)
}
}
Having error state like this can be useful when you want your UI to reflect the fact there's an error. For example, you might change the color of an element until the mutation runs without an error. Instead of having to write the above boilerplate yourself, you can just use the provided result object.
const [mutate, { data, error }] = useMutation(YOUR_MUTATION)
NOTE: While you can use the exposed error state to update your UI, doing so is not a substitute for actually handling the error. You must either provide an onError callback or catch the error in order to avoid warnings about an unhandled Promise rejection.
4.) Lastly, you can also use apollo-link-error to add global error handling for your requests. This allows you to, for example, display an error dialog regardless of where in your application the request originated.
Which of these methods you utilize in your application depends heavily on what you're trying to do (global vs local, state vs callback, etc.). Most applications will make use of more than one method of error handling.
const [mutationHandler, { data, loading }] = useMutation(YOUR_MUTATION, {
onError: (err) => {
setError(err);
}
});
With this we can access data with loading status and proper error handling to avoid any error in console / unhandled promise rejection.
const YOUR_COMPONENT = ({ setError }) => {
// ...
const [mutationHandler, { data, loading }] = useMutation(YOUR_MUTATION, {
onError: (error) => {
setError(error.graphQLErrors[0].message)
}
})
This will handle the errors
When I do a shallow of a component, a method_1() get called in componentDidMount() which call an utility method_2() and finally it makes an call to method_3() which is a promise based request.
It throws the below error when running npm test
Example:
Loginn.spec.js:
const wrapper = shallow(<Login />)
Error: UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
Flow of the code given below.
Login.js:
import { AuthService } from '../../react-auth';
componentWillMount() {
const config = {
environment: 'qa',
google: true
}
const updateLoginState = function (isBool, currentContext) {
currentContext.updateState('authStatusCheck', isBool);
}
authService.init(config, this, updateLoginState)
}
Auth.js
import UmsLib from './umsLib'
export default class AuthService{
init(customOptions, loginContext, updateLoginState) {
// some code.
UmsLib.getStatus(customOptions, loginContext, updateLoginState);
};
}
ums.js
import ums from ‘user-management’;
export class UmsLib{
getStatus(config, loginContext, updateLoginState) {
ums.init(this.configOptions)
.then(user => {
console.log("promise-----------------")
if (user && ums.isReady) {
return updateLoginState(true, loginContext);
}
})
.catch(error => {
throw Error('Failed calling UMS Library', error);
})
}
}
I have added try/catch to throw all the possible error and also tried handling promise in the test case, but it seems I am doing wrong something somewhere. Any suggestion would be appreciated.
That's because of you throw Error inside catch function, which leads to unhandled promise rejection. That's exactly what you got in the console.
The matter is that catch() function returns Promise. From MDN docs:
The Promise returned by catch() is rejected if onRejected throws an error or returns a Promise which is itself rejected; otherwise, it is resolved.
This SOF discussion could also help to understand Promises chaining.
I'm new to React/Redux so I'm building a simple blog app with Redux Form to help me learn. Right now I'm unclear on how I would handle ajax errors when submitting data from the form to the api in my action. The main issue is that I'm using the onSubmitSuccess config property of Redux Form and it seems to always be called, even when an error occurs. I'm really unclear on what triggers onSubmitSuccess or onSubmitFail. My onSubmitFail function is never executed, but my onSubmitSuccess function always is, regardless of whether an error occurred or not.
I read about SubmissionError in the redux-form docs, but it says that the purpose of it is "to distinguish promise rejection because of validation errors from promise rejection because of AJAX I/O". So, it sounds like that's the opposite of what I need.
I'm using redux-promise as middleware with Redux if that makes any difference.
Here's my code. I'm intentionally throwing an error in my server api to generate the error in my createPost action:
Container with my redux form
PostsNew = reduxForm({
validate,
form: 'PostsNewForm',
onSubmit(values, dispatch, props) {
// calling my createPost action when the form is submitted.
// should I catch the error here?
// if so, what would I do to stop onSubmitSuccess from executing?
props.createPost(values)
}
onSubmitSuccess(result, dispatch, props) {
// this is always called, even when an exeption occurs in createPost()
},
onSubmitFail(errors, dispatch) {
// this function is never called
}
})(PostsNew)
Action called by onSubmit
export async function createPost(values) {
try {
const response = await axios.post('/api/posts', values)
return {
type: CREATE_POST,
payload: response
}
} catch (err) {
// what would I do here that would trigger onSubmitFail(),
// or stop onSubmitSuccess() from executing?
}
}
In your case, redux-form doesn't know whether form submission was succeeded or not, because you are not returning a Promise from onSubmit function.
In your case, it's possible to achieve this, without using redux-promise or any other async handling library:
PostsNew = reduxForm({
validate,
form: 'PostsNewForm',
onSubmit(values, dispatch, props) {
// as axios returns a Promise, we are good here
return axios.post('/api/posts', values);
}
onSubmitSuccess(result, dispatch, props) {
// if request was succeeded(axios will resolve Promise), that function will be called
// and we can dispatch success action
dispatch({
type: CREATE_POST,
payload: response
})
},
onSubmitFail(errors, dispatch) {
// if request was failed(axios will reject Promise), we will reach that function
// and could dispatch failure action
dispatch({
type: CREATE_POST_FAILURE,
payload: errors
})
}
})(PostsNew)
For handling asynchronous actions you should use redux-thunk, redux-saga or an other middleware which makes it possible to run asynchronous code.