How to report different errors in React Native application? - error-handling

Is there a solution for reporting different errors in React Native application (iOS and Android) as a global handler?
I am interested in following cases:
Unhandled rejections
Unhandled exceptions
Errors on the native side
By reporting, I mean sending them to some third-party service where you can track errors.

In RN there is a ErrorUtils global handler, that handle uncaught and caught exceptions for your RN JS layer. You can use this to set a handler like:
if (ErrorUtils._globalHandler) {
instance.defaultHandler = ErrorUtils.getGlobalHandler && ErrorUtils.getGlobalHandler() || ErrorUtils._globalHandler;
ErrorUtils.setGlobalHandler(instance.wrapGlobalHandler); //feed errors directly to our wrapGlobalHandler function
}
And handler method
async wrapGlobalHandler(error, isFatal){
const stack = parseErrorStack(error);
//Add this error locally or send it your remote server here
//*> Finish activity
setTimeout (() => {
instance.defaultHandler(error, isFatal); //after you're finished, call the defaultHandler so that react-native also gets the error
if (Platform.OS == 'android') {
NodeModule.reload()
}
}, 1000);
}
Notice in above code you need to create a node module for android only and write a React Native bridge method there in your ReactContextBaseJavaModule:
#ReactMethod
public void reload() {
Activity activity = getCurrentActivityInstance();
Intent intent = activity.getIntent();
activity.finish();
activity.startActivity(intent);
}
Thanks!

Related

Abort an Updates.fetchUpdateAsync() after a certain time [Expo/React native]

Expo React Native SDK Version: 46
Platforms: Android/iOS
Package concerned : Expo.Updates
Hello everyone, I want to programmatically check for new updates, without using the fallbackToCacheTimeout in app.json that will trigger the check of the new updates when the application is launched because like that I can't put a custom loading page.
So by doing this all by code as follow :
try{
const update = await Updates.checkForUpdateAsync();
if(update.isAvailable){
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}else{}
}catch(err){}
But I want to be able to abort all those calls after a certain time (thus, the user that have a bad connection can use the app without waiting a very long time).
I check the documentation and I cannot found any method that allow this.
I dont't think it's possible to cancel a Promise for now in Javascript, or maybe any connection ?
Or does the "fallbackToCacheTimeout" value in the app.json will automatically apply to the fetch updates call of the Expo API?
Do someone have any idea how to do it ? :(
First of all I am assuming you have set updates.checkautomatically field to ON_ERROR_RECOVERY in app.json or app.config.js file. If not, please check the documentation. The reason why you need this is to avoid automatic updates which can also block your app on splash screen.
Updated Solution
Because of the limitation in javascript we can't cancel any external Promise (not created by us or when its reject method is not exposed to us). Also the function fetchUpdateAsync exposed to us is not a promise but rather contains fetch promise and returns its result.
So, here we have two options:
Cancel reloading the app to update after a timeout.
But note that updates will be fetched in background and stored on
the device. Next time whenever user restarts the app, update will
be installed. I think this is just fine as this approach doesn't
block anything for user and also there is a default timeout for http
request clients like fetch and axios so, request will error out in
case of poor/no internet connection.
Here is the code:
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
const updateFetchPromise = Updates.fetchUpdateAsync();
const timeoutInMillis = 10000; // 10 seconds
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject("timedout"), timeoutInMillis))
// This will return only one Promise
Promise.race([updateFetchPromise, timeoutPromise])
.then(() => Updates.reloadAsync())
.catch((error) => {
if (error === 'timedout') {
// Here you can show some toast as well
console.log("Updates were not cancelled but reload is stopped.")
} else if (error === 'someKnownError') {
// Handle error
} else {
// Log error and/or show a toast message
}
})
} else {
// Perform some action when update is not available
}
} catch (err) {
// Handle error
}
Change the expo-updates package just for your app using a patch
Here you can return a cancel method with Updates.fetchUpdateAsync() and use it with setTimeout to cancel the fetch request. I won't be providing any code for this part but if you are curious I can definitely provide some help.
Please refer this section to understand use of fallbackToCacheTimeout in eas updates.
Old solution:
Now, for aborting or bypassing the promise i.e. Updates.fetchUpdateAsync in your case. You can basically throw an Error in setTimeout after whatever time duration you want, so that, catch block will be executed, bypassing the promises.
Here is the old code :
try{
const update = await Updates.checkForUpdateAsync();
if(update.isAvailable){
// Throw error after 10 seconds.
const timeout = setTimeout(() => { throw Error("Unable to fetch updates. Skipping..") }, 10000)
await Updates.fetchUpdateAsync();
// Just cancel the above timeout so, no error is thrown.
clearTimeout(timeout)
await Updates.reloadAsync();
}else{}
}catch(err){}

React Native websockets with hooks INVALID_STATE_ERR

React Native wont send multiple messages on state change throught websocket. Server recives 1st string normally and client gets console logs on every state change. Any idea why I get an error about state?
useEffect(() => {
socket.onopen = function (e) {
socket.send(String(state?.x));
};
socket.send("test") <-- this one does not work
console.log("send")
socket.onmessage = function () {
console.log("message")
}
socket.close();
}, [state])
Returns error INVALID_STATE_ERR
So basically when using react native with EXPO you will get those errors. After the application is build and installed (on the same phone) the application works.

How to use Blur event for AppState in react native

Please I need an example on how to use the 'blur' event for react native AppState. I am trying to respond to when the app is not in focus e.g. when the user pulls the notification drawer but I keep getting the error message Invariant Violation: Trying to subscribe to unknown event: "blur".
Based on the tags associated with the commit that this feature landed in (https://github.com/facebook/react-native/commit/d45818fe47c53a670db933cf805910e227aa79c9) it seems like that is only available starting in 0.61 and hasn't landed in a stable release yet. Make sure you're running 0.61.0-rc.0 or later.
According to documentation . Blur is [Android only]
"[Android only] Received when the user is not actively interacting with the app. Useful in situations when the user pulls down the notification drawer. AppState won't change but the blur event will get fired."
if you still want to use it for android you can use it with condition for android only
import { Platform } from "react-native";
........
componentDidMount() {
if (Platform.OS === "android") {
AppState.addEventListener("blur", this._handleAppStateBlur);
}
}
componentWillUnmount() {
if (Platform.OS === "android") {
AppState.removeEventListener("blur", this._handleAppStateBlur);
}
}
_handleAppStateBlur = () => {
console.log("blur");
};
According to the docs mentioned in the official react native documentation, there are three states supported by AppState:
active - The app is running in the foreground.
background - The app is running in the background. The user is either:
in another app
on the home screen
[Android] on another Activity (even if it was launched by your app)
[iOS] inactive - This is a state that occurs when transitioning between foreground & background, and during periods of inactivity such as entering the Multitasking view or in the event of an incoming call.
Since there is no such state as blur, therefore you are facing an error saying that it could not find such event.
Edit
You have to register blur as an event in your component lifecycle, but you have to be cautious here and have to determine the Platform before registering blur event as it is available in android only and not in ios.
To register an event you have to do this:
import React from 'react';
import {AppState} from 'react-native';
class HandlingEvents extends React.Pure.Component {
constructor(props) {
super(props)
// your state goes here...
}
componentDidMount() {
// your event will be registered here, when your component is mounted on // the screen.
// Be cautious here, make a platform check here so as to avoid discrepancies in ios devices
AppState.addEventListener('blur',this.handleBlurState)
}
componentWillUnMount() {
// your event will be removed here, when your component gets unmounted from the screen.
// Be cautious here, make a platform check here so as to avoid discrepancies in ios devices
AppState.removeEventListener('blur',this.handleBlurState)
}
handleBlurState = (nextAppState) => {
//this method will contain your entire logic, as to how you want to treat your component in this event.
// As per the docs, since the state of your app will not changed, therefore you can continue your logic here by checking if the state of your app is **change** or not..
if (AppState.currentState === "active" && nextAppState === "active") {
//whatever task you want to perform here..;
}
}
}

ES6 Proxy not defined in React Native when not in remote debug mode?

I'm wrapping an imported SDK class with a Proxy so that I can eventually catch RequestExceptions, i.e. when there is no network connection to display error popups.
The app is working without issues in remote debugging mode, however, when I disable it the error Can't find Variable: Proxy is thrown. Do I need to import this explicitly somehow? Or is there an alternative method to wrap a class so that I can catch all of its exceptions?
Below is the code for the Proxy wrapper.
import Backend from 'backend-sdk';
import RequestException from 'backend-sdk/src/exceptions/RequestException';
let handler = {
get: (target, name, receiver) => {
try {
return Reflect.get(target, name, receiver);
} catch (e) {
if (e instanceof RequestException) {
console.error(e);
//TODO Add a toast notification for failed API requests
} else {
throw e;
}
}
}
};
export default new Proxy(new Backend(), handler);
Proxy is not pollyfilled in react native by default. It works in chrome debugger because react native uses chrome js engine during debugging see Document on js environment. You may try using Proxy pollyfill.
Note that as of react-native 0.59, react-native on Android now uses a more modern version of JavaScriptCore that includes Proxy support.

how to catch uncaught exception (globally) in react native

Does anyone know what is the best way to catch all uncaught exception (globally) so that I can send a crash report back to the server? I don't seem to be able to find any information on the react native docs or on github.
You could possibly override the exception logging that React Native uses for development:
ErrorUtils.setGlobalHandler(function() {
// your handler here
});
https://github.com/facebook/react-native/blob/522fd33d6f3c8fb339b0dde35b05df34c1233306/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js#L46
You may then need to write some Obj-C which you expose to JS, depending on your exact requirements.
This is how I'd do it:
Step 1: We intercept react-native error handler like so:
//intercept react-native error handling
if (ErrorUtils._globalHandler) {
this.defaultHandler = ErrorUtils.getGlobalHandler && ErrorUtils.getGlobalHandler() || ErrorUtils._globalHandler;
ErrorUtils.setGlobalHandler(this.wrapGlobalHandler); //feed errors directly to our wrapGlobalHandler function
}
Step 2: Now our wrapGlobalHandler gets called whenever theres an unhandled error. So do anything you want with the error within this function.
Then do something with the error:
async function wrapGlobalHandler(error, isFatal){
const stack = parseErrorStack(error);
//do anything with the error here
this.defaultHandler(error, isFatal); //after you're finished, call the defaultHandler so that react-native also gets the error
}
Full code here:
import stacktraceParser from 'stacktrace-parser';
const parseErrorStack = (error) => {
if (!error || !error.stack) {
return [];
}
return Array.isArray(error.stack) ? error.stack :
stacktraceParser.parse(error.stack);
};
// intercept react-native error handling
if (ErrorUtils._globalHandler) {
this.defaultHandler = (ErrorUtils.getGlobalHandler
&& ErrorUtils.getGlobalHandler())
|| ErrorUtils._globalHandler;
ErrorUtils.setGlobalHandler(this.wrapGlobalHandler); // feed errors directly to our wrapGlobalHandler function
}
async function wrapGlobalHandler(error, isFatal) {
const stack = parseErrorStack(error);
//do anything with the error here
this.defaultHandler(error, isFatal); //after you're finished, call the defaultHandler so that react-native also gets the error
}
Thats it!
You can try https://github.com/master-atul/react-native-exception-handler.
A react native module that lets you to register a global error handler that can capture fatal/non fatal uncaught exceptions. The module helps prevent abrupt crashing of RN Apps without a graceful message to the user.
There is a native way.
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
moduleProvider:^{
id<RCTExceptionsManagerDelegate> customDelegate = ...
return #[[RCTExceptionsManager initWithDelegate:customDelegate];
}
launchOptions:nil];
Just put your report logic in the customDelegate.
There's now react-native-error-reporter, which pretty much does the trick in a very simple way:
npm i react-native-error-reporter --save
rnpm link
Then add this lines to your code:
import ErrorReporter from 'react-native-error-reporter';
ErrorReporter.init("vanson#vanportdev.com", "My App's Crash Report");
In case you're using Redux, you might wanna try redux-catch middleware.