Why graphQLErrors are always empty in react components? - reactjs

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

Related

React-query causes the whole React tree to unmount on response error

As it is said here React docs, on uncaught errors the whole tree will unmount.
Query 1:
const { data: post } = useQuery<PostResponseDto, AxiosError>(
['fetch-post', params],
() => fetchPost(params!.postId),
{
refetchOnMount: true,
onError: (err) => {
// do something with the error
},
},
);
Query 2:
const {
data: postCommentsGroups,
fetchNextPage,
isFetchingNextPage,
hasNextPage,
refetch,
} = useInfiniteQuery<PostCommentsResponseDto, AxiosError>(
['fetch-post-comments', params],
({ pageParam = 0 }) => fetchPostComments(params!.postId, pageParam),
{
refetchOnMount: true,
getNextPageParam: (lastPage) => {
if (!lastPage.data.nextPage) {
return false;
} else {
return lastPage.data.nextPage;
}
},
onError: (err) => {
// do something with the error
},
},
);
Fetch functions:
export const fetchPost = async (webId: string) => {
const response = await axiosInstance.get(`${API_URL}/post/${webId}`);
return response.data;
};
export const fetchPostComments = async (webId: string, page: number) => {
const response = await axiosInstance.get(
`${API_URL}/post/${webId}/comments?page=${page}&limit=${COMMENTS_LIMIT}`,
);
return response.data;
};
The problem: When these queries result in status code !== 200, uncaught errors appear in the console (see below) and the whole React tree unmounts. This definitely is not the behavior I want. E.g. on both of these queries I get 404 if postId is incorrect and when this happens I want to do certain actions in onError callback (show some info to the user), but this impossible due to the uncaught errors by react-query and React unmounting the whole tree.
This is one of the few uncaught errors (AxiosErrors).
Why is this happening?
P.S.: I don't think this happens with useMutate() hook.
P.P.S: I am using global ErrorBoundary but that is irrelevant for this problem. I want to manipulate the DOM in that specific component in which queries are being made/errored.

How Can I Destructure error from is RTK Query Mutation Hook?

I am using redux toolkit query for my data fetching API.
The problem is I am having is error handling redux toolkit returns an error property from the query hook. So the error that is returned from the server is an object with nested data and what trying to access it I get a typescript error when trying to access the data from the error object.
below is how I am declaring my mutation hook
const [updateProgram, {
isLoading: isUpdatingProgram,
error: updateProgramError
}] = useUpdateProgramMutation();
below is how I try to access the error in my code.
onSubmit = {
async(values, {
setSubmitting
}) => {
await updateProgram({ ...values,
id: 'programData.data._id'
});
refetch();
if (updateProgramError) {
enqueueSnackbar('Test message', {
variant: 'error'
});
console.log(updateProgramError?.data?.message);
}
setSubmitting(false);
}
}
now the typescript error I am getting is as below.
Not that I able to console.log(updateProgramError) and is the data in the console
Property 'data' does not exist on type 'FetchBaseQueryError | SerializedError'.
Property 'data' does not exist on type 'SerializedError'.
below is updateProgramError when I log is as a whole on the console
{
"status": 500,
"data": {
"status": "error",
"message": "Something went very wrong!"
}
}
below is how I have implemented useUpdateProgramMutation().
import { ndovuAPI } from './ndovuAPI';
export const ndovuProgramsAPI = ndovuAPI.injectEndpoints({
endpoints: builder => ({
getProgramById: builder.query({
query: (id: string) => `/programs/${id}`,
providesTags: ['Programs']
}),
getAllPrograms: builder.query({
query: queryParams => ({ url: `/programs/`, params: queryParams }),
providesTags: ['Programs']
}),
registerProgram: builder.mutation({
query: body => ({
url: '/programs',
method: 'POST',
body
}),
invalidatesTags: ['Programs']
}),
updateProgram: builder.mutation({
query: body => ({ url: `/programs/${body.id}`, method: 'PATCH', body }),
invalidatesTags: ['Programs']
})
}),
overrideExisting: true
});
// Export hooks for usage in functional components
export const { useGetProgramByIdQuery, useGetAllProgramsQuery, useUpdateProgramMutation } = ndovuProgramsAPI;
The code as you have it written will not work the way you want. You can't reference the error from the hook inside of an onSubmit handler like that. What you'll want to do is this:
const onSubmit = async (values) => {
try {
// unwrapping will cause data to resolve, or an error to be thrown, and will narrow the types
await updateProgram({
...values,
id: 'programData.data._id'
}).unwrap();
// refetch(); // you should most likely just use tag invalidation here instead of calling refetch
} catch (error) {
// error is going to be `unknown` so you'll want to use a typeguard like:
if (isMyKnownError(error)) { // add a typeguard like this
enqueueSnackbar('Test message', {
variant: 'error'
});
} else {
// You have some other error, handle it differently?
}
}
}
References:
https://redux-toolkit.js.org/rtk-query/usage/mutations#frequently-used-mutation-hook-return-values
From what I can infer from this TS error, the type of updateProgramError seems like an union of types 'FetchBaseQueryError', 'SerializedError' and possibly other types, which means that TS only assumes that you can safely access this data property after ensuring that data exists in the object.
In other words, TS does not know which of these types updateProgramError will be unless you do some checking to ensure that.
if (updateProgramError) {
enqueueSnackbar('Test message', {
variant: 'error'
});
if ('data' in updateProgramError) console.log(updateProgramError.data.message);
}

Multiple useLazyQuery hooks (Apollo Client) in React Function Component

I am trying to include two Apollo-Client useLazyQuery hooks in my function component. Either works fine alone with the other one commented out, but as soon as I include both, the second one does nothing. Any ideas?
export default function MainScreen(props) {
useEffect(() => {
validateWhenMounting();
}, []);
const [validateWhenMounting, { loading, error, data }] = useLazyQuery(
validateSessionToken,
{
onCompleted: (data) => console.log('data', data),
},
);
const [validate, { loading: loading2, error: error2, data: data2 }] =
useLazyQuery(validateSessionTokenWhenSending, {
onCompleted: (data2) => console.log('data2', data2),
});
const handleSendFirstMessage = (selectedCategory, title, messageText) => {
console.log(selectedCategory, title, messageText);
validate();
};
Figured it out: Adding the key-value pair fetchPolicy: 'network-only', after onCompleted does the trick. It seems that otherwise, no query is being conducted due to caching...
This is the pattern that I was talking about and mentioned in the comments:
const dummyComponent = () => {
const [lazyQuery] = useLazyQuery(DUMMY_QUERY, {variables: dummyVariable,
onCompleted: data => // -> some code here, you can also accept an state dispatch function here for manipulating some state outside
onError: error => // -> you can accept state dispatch function here to manipulate state from outside
});
return null;
}
this is also a pattern that you are going to need sometimes

handling apollo-server errors on client

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 };
};

Error handling Apollo-client with redux-observable

I'm doing a mutation using Apollo-client and redux-observable and so far this is my code:
export const languageTimeZoneEpic = (action$) => {
return action$.ofType('PING')
.flatMap(action => client.mutate({
mutation: languageTimeZoneIdMutation,
variables: { id: action.id, defaultLanguage: action.selected_language, defaultTimeZoneId: action.selected_timeZone }
})
.then(store.dispatch(setLocale(action.selected_language)))
)
.map(result => ({
type: 'PONG',
payload: result
}))
.catch(error => ({
type: 'PONG_ERROR'
}));
};
My mutation works correctly but I can't seem to make my catch(error) work. In the small amount of documentation I've found on this, it suggests I put Observable of after error => but then it gives me an error saying Observable is undefined.
Thank you
UPDATE:
If the connection between the app and the server doesn't work, it just waits for the connection to come back up and then finish the epic. I would like for it to just catch and error and stop the epic.
Figured out there was nothing wrong with the catch(error) in the code above. Turns out putting the server on hold did not throw an error when I tried to establish a connection. It had to be shut down for it to throw an error.
For those who are looking for a good way to implement a mutation using Apollo-client with redux-observable (because there is almost no docs on it), here's the code I used for my mutation:
import { languageMutation } from '../mutation/languageMutation';
import { changeLanguageFulfilled, changeLanguageError } from '../actions/actions';
export const languageEpic = (action$) => {
return action$.ofType('CHANGE_LANGUAGE')
.mergeMap(action => client.mutate({
mutation: languageMutation,
variables: { id: action.id, defaultLanguage: action.selected_language, defaultTimeZoneId: action.selected_timeZone }
}).then(result => changeLanguageFulfilled(result))
.catch(error => changeLanguageError(error))
);
};
Both changeLanguageFulfilled and changeLanguageError are actions that trigger their own reducer.
Hope this helps someone.

Resources