I'm trying to add error catching to the render function of a component. This works fine when I throw an error in the actual render function, but if there are errors in the children of the component, the try does not catch the errors (or they are intercepted by the child component error handlers, I'm not sure?)
Is there anyway to force the errors to the parent.
const SimpleComponent = React.createClass({
render: function(){
try{
throw 'new error'
return <div>{this.props.children}</div>
}catch(e){
console.log('error', e);
}
}
})
The above works
const SimpleComponent = React.createClass({
render: function(){
try{
return <div>{this.props.children}</div>
}catch(e){
console.log('error', e);
}
}
})
const ChildComponent = React.createClass({
render: function(){
throw 'child error'
}
})
<SimpleComponent>
<ChildComponent />
</SimpleComponent>
This above does not catch
Use componentDidCatch() method from react 16.
Check this for more info
You can leverage React's BatchingStrategy API to easily wrap a try/catch around all of your React code. The benefit of this over window.onerror is that you get a nice stack trace in all browsers. Even modern browsers like Microsoft Edge and Safari don't provide stack traces to window.onerror.
Note that this solution won't always prevent React from getting into a bad state. However, this solution will at least allow you to handle the error, for example displaying an error banner/modal, or sending stack trace error logs to your service.
Here's what it looks like with React 15.4:
import ReactUpdates from "react-dom/lib/ReactUpdates";
import ReactDefaultBatchingStrategy from "react-dom/lib/ReactDefaultBatchingStrategy";
let isHandlingError = false;
const ReactTryCatchBatchingStrategy = {
// this is part of the BatchingStrategy API. simply pass along
// what the default batching strategy would do.
get isBatchingUpdates () { return ReactDefaultBatchingStrategy.isBatchingUpdates; },
batchedUpdates (...args) {
try {
ReactDefaultBatchingStrategy.batchedUpdates(...args);
} catch (e) {
if (isHandlingError) {
// our error handling code threw an error. just throw now
throw e;
}
isHandlingError = true;
try {
// replace this with whatever error handling logic you like.
// e.g. dispatch redux action notifying the app that an error occurred:
// `store.dispatch({type: "EXCEPTION", payload: e});`
console.error(e);
} finally {
isHandlingError = false;
}
}
},
};
ReactUpdates.injection.injectBatchingStrategy(ReactTryCatchBatchingStrategy);
Full writeup here: https://engineering.classdojo.com/blog/2016/12/10/catching-react-errors/
I'd recommend the (currently) unstable lifecycle event unstable_handleError in your parent component.
public unstable_handleError(err: any) {
this.setState({
error: err
});
}
The error hook will likely become official API in future releases.
See this issue for details
https://github.com/facebook/react/issues/2461
Related
We use Vue with apollo and I have difficulties to handle errors properly.
This is on of our components
<template>
<div v-if="product">
<router-view :key="product.id" :product="product" />
</div>
<div v-else-if="!this.$apollo.loading">
<p>Product is not available anymore</p>
</div>
</template>
<script>
import productQuery from "#/graphql/product.graphql";
export default {
name: "ProductWrapper",
props: ["productId"],
apollo: {
product: {
query: productQuery,
variables() {
return {
productId: this.productId,
};
},
},
},
};
</script>
If the product is not available anymore, we have three options in the backend:
a) the backend just can send null without errors
b) send an error object as part of the data with unions
c) send some error extensions with apollo for easy error handling in the client
Option a) seems to be a strange option
Option b) is too complicated for our use case.
So I decided for option c):
In our backend we use apollo error extensions to send some proper errorCode for the client to handle.
{
data: { }
errors: [
{
"message": "NOT_FOUND",
"locations": ...,
"path": ...
"extensions": {
"details": [],
"errorCode": "NOT_FOUND",
"message": "NOT_FOUND",
"classification": "DataFetchingException"
}
]
}
Everything works fine as product results in null anyway as no data is sent, just an error.
But vue-apollo is logging this to console.error. I don't want any logging to console.error as the user sees an red mark in his browser. But I want it to pop up in the console.error if nobody else has handled this error.
I can add an error handling in three places:
error() inside query definition
$error() default handler for all queries
ErrorLink
ErrorLink seems to be the wrong place as only the query in the component knows that NOT_FOUND is not fatal but can happen sometimes. Same is true for $error
So how do I say: this error might happen, I am well prepared for this. All other errors should be handled by the ErrorLink. How can I consume an error in my component?
My overview over vue-apollo error handling.
SmartQuery error handler
Documentation: https://apollo.vuejs.org/api/smart-query.html
error(error, vm, key, type, options) is a hook called when there are
errors. error is an Apollo error object with either a graphQLErrors
property or a networkError property. vm is the related component
instance. key is the smart query key. type is either 'query' or
'subscription'. options is the final watchQuery options object.
Not documented: If you return false the error processing is stopped and the default error handler (Apollo Provider) is not called. If you return true or do not return anything (aka undefined) the default error handler is called.
Code Example
export default {
name: "ProductWrapper",
props: ['productId'],
apollo: {
product: {
query: productQuery,
variables() {
return {
productId: this.productId
}
},
error(errors) {
console.log("Smart Query Error Handler")
console.log(errors.graphQLErrors)
return false;
}
}
}
}
VueApolloProvider errorHandler
Documentation: https://apollo.vuejs.org/api/apollo-provider.html
Global error handler for all smart queries and subscriptions
Important: This is NOT for mutations.
Not documented: This is not called when an error handler of a smart query returned false
Code Example
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
errorHandler: (errors) => {
console.log("Provider errorHandler")
console.log(errors)
}
});
VueApolloProvider $error special option (useless)
Documentation
https://apollo.vuejs.org/guide/apollo/special-options.html
$error to catch errors in a default handler (see error advanced options > for smart queries)
Code Example (wrong)
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
error(errors) {
console.log("Provider $error")
console.log(errors)
}
}
});
This was my first try, but it results in a call when the component is mounted and gives us the complete component. See https://github.com/vuejs/vue-apollo/issues/126
Code Example (kind of ok?)
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
$error() {
return (errors) => {
console.log("Provider $error handler")
console.log(errors.graphQLErrors)
}
},
},
});
This way the $error function is called.
But it is called just like the default errorHandler (see above). the documentation seems to be wrong. So this method looks rather useless to me. As you can see in this debugging screenshot, it is used just like the other error handlers:
ErrorLink
Documentation: https://www.apollographql.com/docs/react/data/error-handling/#advanced-error-handling-with-apollo-link
Code example
import {onError} from "apollo-link-error";
const errorHandler = onError(({ networkError, graphQLErrors }) => {
console.log("Link onError")
console.log({ graphQLErrors, networkError})
})
const link = split(
// split based on operation type
({query}) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
},
wsErrorHandler.concat(wsLink),
errorHandler.concat(httpLink)
);
Important: this is called before any other ErrorHandler and it works for queries, subscriptions and mutations
Mutation catch
Documentation https://apollo.vuejs.org/guide/apollo/mutations.html
Code example 1
async submit() {
await this.$apollo.mutate({
mutation: productDeleteMutation,
variables: {
productId: this.product.id
},
}).catch((errors) => {
console.log("mutation .catch error")
console.log(errors)
})
}
Code example 2
You can use try/catch too:
async submit() {
try {
await this.$apollo.mutate({
mutation: productDeleteMutation,
variables: {
productId: this.product.id
},
})
} catch (errors) {
console.log("mutation try/catch error")
console.log(errors)
}
}
the only other handler which can be called is the onError ErrorLink (see above), which would be called before the catch error handling.
Mutation handled by vue with errorCaptured
Add this lifecycle hook to your component to catch errors from mutations.
errorCaptured(err, vm, info) {
console.log("errorCaptured")
console.log(err.graphQLErrors)
return false
}
Documentation: https://v2.vuejs.org/v2/api/#errorCaptured
More links: https://medium.com/js-dojo/error-exception-handling-in-vue-js-application-6c26eeb6b3e4
It works for mutations only as errors from smart queries are handled by apollo itself.
Mutation errors with vue global errorHandler
You can have a vue default error handler to catch graphql errors from mutations
import Vue from 'vue';
Vue.config.errorHandler = (err, vm, info) => {
if (err.graphQLErrors) {
console.log("vue errorHandler")
console.log(err.graphQLErrors)
}
};
It works for mutations only as errors from smart queries are handled by apollo itself.
Documentation https://v2.vuejs.org/v2/api/#errorHandler
window.onerror
Errors outside vue can be handled with plain javascript
window.onerror = function(message, source, lineno, colno, error) {
if (error.graphQLErrors) {
console.log("window.onerror")
console.log(error.graphQLErrors)
}
};
This does not work for mutations, queries or subscriptions inside or outside vue components as these are handle by vue or apollo itself (and printed to console.err if not handled)
Documentation https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror
Summary
The only way to catch errors for mutations AND queries at the same time is onError with an ApolloLink. Server, Network and Authentication errors should be catched there as these are not specific to any operation.
Additional notes
Approach how to handle errors in the backend
https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces/
I'd like to catch the error in component level and prevent propagation while using the useQuery in #apollo/react-hook.
Here is my example code
const invitationDocument = gql`
query DecodeInvitation($token: String!) {
DecodeInvitation(token: $token) {
name
email
}
}
`
const InvitationPage = (props) => {
const { data, error, loading } = useQuery(invitationDocument, {variables: { token: "XXXX" }});
if(error)
{
return <InvitationErrorPage error={error.message}/>
}
return loading? <LoadingPage> : <InvitationAcceptPage />
}
It works fine but at the same time, the error is being propagated to its parents level so I get another error notification message which comes from the error handler at the global level.
At the application level, I use the apollo-link-error to manage the Graphql errors.
import { onError } from 'apollo-link-error';
const errorLink = onError (({ graphqlErrors, networkError }) => {
if(graphqlErrors)
notification.error(graphqlErrors[0].message);
});
const client = ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.from([
errorLink,
new HttpLink({ uri: `http://localhost:8080/graphql`})
])
})
For now, I am finding a solution to stop propagation to top-level so that I can show only InvitationErrorPage and stop displaying error notification at the global level.
I was also trying to prevent errors from being logged in an Error Link by handling them on a useQuery hook and a further delve into the ApolloLink documentation helped clear up what is happening. The key misunderstanding is that the Error Link is not a parent- or application-level handler, it is request middleware. It's helpful to think about how the data is coming back from the server:
Thus, when you see an error notification from the Error Link it is not something that "propagated up" from the useQuery hook: it occurred in the request path before the useQuery result was available on the client.
Thus, the onError callback for the Error Link will always be called before any error handling code in the useQuery hook.
Probably your best bet is to use a combination of the operation and graphQLErrors[x].extensions to figure out what errors you should pass through the Error Link middleware like so:
const errorLink = onError(({operation, response, graphQLErrors}) => {
if (!graphQLErrors) {
return;
}
if (operation.operationName === "DecodeInvitation") {
for (const err of graphQLErrors) {
if (err.extensions?.code === 'UNAUTHENTICATED') {
// Return without "notifying"
return;
}
}
}
// Notify otherwise
notification.error(graphqlErrors[0].message);
})
here is what i do, and i'am not realy sure its correct :
//store
async addUser({commit}) {
try {
const {data} = await apiService.addUser()
commit('SET_USER', data)
commit('SET_NOTIFICATION', {type:'success', message: 'user successfuly created'})
} catch (error) {
commit('SET_NOTIFICATION', {type:'error', message:error})
}
}
SET_USER(state, user) {
state.users.push(user)
}
//my component:
async addUser() {
this.isLoading = true
await this.$store.dispatch('updatePatient', this.form)
this.isLoading = false
}
is it legit ?
sometimes i think i would need more logic inside my component depending on the succes or rejected api request. Should i put all the logic in my actions ? like i do at the moment ?
Maybe should I add a status state for each actions, for example :
state {
users: []
postUserSuccess: null
postUserError: false
updateUserSuccess: null
updateUserError: false
// ...
}
and do what i want in the component with a computed property mapped to the store ?
What do you think ?
I don't know if it's a best practice but I let the components the exception handling. That method has its pros (you don't have to pollute the state with error management) and cons (you have to repeat the error management code for every action call).
All service calls will be made in actions
The state will only be set in mutations.
All service calls will return a promise with a resolve(data to load in the state) and a reject(message errors to present).
There will be an interceptor to reject the response in case there's a custom error (here you can put if the response has an error prop reject the response and send as an error the error prop, now you don't have to deconstruct the response in the action).
I'm going to give you a simplified example (I use axios, you can learn how to do it with the library that you use).
Actions in Vuex are asynchronous. So you don't need to try/catch them.
ApiService - Add User
const addUser = () => {
return new Promise((resolve, reject) => {
axios
.post(url, user)
.then(response => resolve(response.data))
.catch(error => reject(error));
});
};
store
async addUser({commit}) {
const data = await apiService.addUser();
commit('SET_USER', data);
return data;
}
if the promise in apiService.addUser is resolved the commit is going to be made if is rejected axios will return the promise and you can catch the error in the component that calls the action.
Component
async addUser() {
this.isLoading = true;
try {
await this.$store.dispatch('updatePatient', this.form);
} catch (error) {
// here goes the code to display the error or do x if there is an error,
// sometimes I store an errors array in the data of the component other times I do x logic
}
this.isLoading = false;
}
State
Your state will be cleaner now that you don't need to store those errors there.
state {
users: []
}
I'm trying to make an singleton Class that uses AsyncStorage.
Follow the code:
import ReactNative, { AsyncStorage } from "react-native";
export default class StorageHandler {
static async getItem(key) {
try {
return await AsyncStorage.getItem(key);
} catch (error) {
console.log(error);
}
}
static async setItem(key, value) {
console.log("Values on StorageHandler", key, value);
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.error(error);
}
}
}
I'm using the setItem function in a function in a component, follow the function:
async onIds(device) {
console.log("Device info: ", device);
try {
await StorageHandler.setItem(StorageConstants.ONESIGNAL_ID, device.userId)
} catch (error) {
console.log(error);
}
}
But i'm getting an error :
ReferenceError: value is not defined;
The log on StorageHandler returns :
Values on StorageHandler onesignal_id xxxxx-xxxxx-xxxx-xxxx-xxxxxx
So, what i'm doing wrong ?
And most important, why is it happening?
I'm starting at react-native so i'm a bit lost.
Edit1 :
Removed Resolve from getItem catch.
I deleted the react-native app from my device and recompile without livereload and it worked.
Maybe it's something wrong with livereload.
Edit: The chrome debugger by default have the option "Pause on Exceptions" on.
Sometimes the promises didn't return an error, and by the act of livereload it seems the chrome is pausing the promises doe to the previous error.
Just turn "Pause on Exceptions" off and it work like a charm.
Challenge is to catch network offline status error during POST request using AXIOS library in React-Native.
axios.post(Constants.API.LOGIN, {
username: email,
password: password
})
.then(function (response) {
console.log('SUCCESS!');
loginUserSuccess(dispatch, user);
})
.catch(function (error) {
if(!error.response){
networkError(dispatch);
} else {
loginUserFail(dispatch);
}
});
but when I switch off WiFi getting error
Possible Unhandled Promise Rejection (id:0)
What is the way to handle network statuses with AXIOS?
Thanks!
Check out NetInfo:
import { NetInfo } from 'react-native'
https://facebook.github.io/react-native/docs/netinfo.html
isConnected
Available on all platforms. Asynchronously fetch a boolean to determine internet connectivity.
NetInfo.isConnected.fetch().then(isConnected => {
console.log('First, is ' + (isConnected ? 'online' : 'offline'));
});
function handleFirstConnectivityChange(isConnected) {
console.log('Then, is ' + (isConnected ? 'online' : 'offline'));
NetInfo.isConnected.removeEventListener(
'change',
handleFirstConnectivityChange
);
}
NetInfo.isConnected.addEventListener(
'change',
handleFirstConnectivityChange
);
You may have a bit of extra issue there with your handling. I can't see exactly what is triggering the unhandled rejection in your code, but I can assume it is making it into the catch block.
If so, that currently means that either networkError() or loginUserFail() are failing, which is what is actually generating the unhandled rejection.
Take a look at those functions and make sure they have catch blocks inside them if they are asynchronous. Ask yourself where exactly is that error coming from, and answer that first, then look at NetInfo. Otherwise, the unhandled rejection may still be able to occur. Make sure you test all the different ways the code could fail :)
With NetInfo, you could set up something near the root-level in your app architecture, by way of that event listener. This could allow you to manage a setting in something like Redux. If the app detects offline, you could set the value to false and the entire app would know it is offline. You could probably make a middleware.
The code might look something like:
middleware:
`store.dispatch(changeConnectivityState())`
or you could have an action creator (shown with redux-thunk):
export function onAppConnectivityChange(newState) {
return (dispatch) {
dispatch({
type: APP_CONNECTIVITY_CHANGE,
payload: newState
})
}
}
Redux reducer:
case APP_CONNECTIVITY_CHANGE:
return {
...state,
isConnected: action.payload
}
In your components:
render() {
if (this.props.isConnected === true) {
return <View><Text>We are connected.</Text></View>
}
}
const mapStateToProps = (state) => {
const isConnected = state.someReducer.isConnected
return {
isConnected
}
}
export default connect(mapStateToProps, null)(SomeComponent)
Hopefully that gives you some ideas. Axios shouldn't be responsible for checking if the app is online or offline (except for quickly checking against a boolean). Your app should always know if it online or offline, and your components should be able to detect the state. You will find that much more scalable and easier to work with.
If I saw where you were calling Axios from, I could give you a more specific answer.