How do I gracefully prevent crashes in react native?

Viewed 3483

I would like to gracefully show an empty View when any error occurs (syntax, undefined, type errors, etc.)

This is what I've tried, but it doesn't seem to fail gracefully. The whole app still crashes with this implementation.

const Parent = (props) => {
    try{
        return (<Child/>) //if Child logic crashes for any reason,  return a blank view.
    }catch(err){
        return <View/>
    }
}
5 Answers

You can use ErrorBoundary https://reactjs.org/docs/error-boundaries.html

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

Using in root project (example App.js)

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

Installion

just install

yarn add react-native-error-boundary

HOC

make HOC like this

const CustomFallbackComponent = (props: { error: Error, resetError: Function }) => (
  <View>
    <Text>Something happened!</Text>
    <Text>{props.error.toString()}</Text>
    <Button onPress={props.resetError} title={'Try again'} />
  </View>
)

const withErrorBoundries = (props) => (
  <ErrorBoundary FallbackComponent={CustomFallbackComponent}>
    {props.children}
  </ErrorBoundary>
)

Usages

wrap every that component with can throw exception like this

export default withErrorBoundries(ComponentA);

or
export const ComponentC=withErrorBoundries(ComponentB)

use the ?. operator and the try{} catch(error){} it will prevent your app from crashes. here is the example you can try in functional programming.

import React, {useEffect, useState } from 'react';
import {Text, TouchableOpacity , View} from 'react-native'

// in order to prevent app from crash use this method

function ShowErrorMessageCompnent () {
    const [error, setError] = useState(false)
    const [item, setItem] = useState({
        id:"1",
        name:"ab"
    })
    const [showerrormessage , setShowErrorMessage] = useState("")
    // if you have an object but unsure of the attibute sof that use this 

    useEffect(() => {
       try{
          let fathername = item?.fathername?.a   // this will thro no Errror
           //let fname = item.fathername.a   // this will throw Error
            //setUnknowfunction("Erro")  // as i dont have this function so it will thorw error
       }catch(err){
        console.log(err)
        setShowErrorMessage(err.message)
        setError(true)
       }
    },[])
   

    if(error){
        return(
            <TouchableOpacity style={{flex:1,justifyContent:"center", alignItems:"center",}}>
                <Text style={{color:"#000"}}>{showerrormessage}</Text>
            </TouchableOpacity>
        )
        }

    return (
            <View style={{flex:1, justifyContent:"center", alignItems:"center",}}>
                <Text style={{color:"#000", fontWeight:"bold"}}>No error</Text>
            </View>
    )
  
  }

  export default ShowErrorMessageCompnent;

All you need is ErrorBoundary, the preferred way is to set up a HOC(Higher-order component) that can handle errors and prevent the app being crashed. You can also create ErrorBoundary component. Here is an example of HOC that might help you.

import React, { Component } from 'react';

export const withErrorBoundary = WrappedComponent => (
  class extends Component {
    constructor(props) {
      super(props);
      this.state = { error: null, errorInfo: null };
    }

    componentDidCatch = (error, errorInfo) => catchFunc(error, errorInfo, this)

    render() {
      if (this.state.errorInfo) {
        return handleError(this)
      }
      // Normal, just render children
      return <WrappedComponent {...this.props} />;
    }
  }
);

const catchFunc = (error, errorInfo, ctx) => {
  // catch errors in any components below and re-render with error message
  ctx.setState({
    error: error,
    errorInfo: errorInfo
  })
  // log error messages, etc.
}

const handleError = (ctx) => (
  // Error path
  <div style={ctx.props.style || styles.error}>
    <h2>Something went wrong.</h2>
    <details style={{ whiteSpace: 'pre-wrap' }}>
      {ctx.state.error && ctx.state.error.toString()}
      <br />
      {ctx.state.errorInfo.componentStack}
    </details>
  </div>
);

const styles = {
  error: {
    backgroundColor: '#f98e7e',
    borderTop: '1px solid #777',
    borderBottom: '1px solid #777',
    padding: '12px',
  }
}

export default withErrorBoundary;

Here, the withErrorBoundry is a higher-order component responsible to capture any error in wrapped component through componentDidCatch. All you need to do is just export your component with error boundary HOC and it will take care of the underlying error. Here is the example usage.

import React from 'react';
import { Text } from 'react-native';
import withErrorBoundary from './withErrorBoundary';

const ExampleComponent = (props) => (
    <Text>{ props.user.name }</Text>
);

export default withErrorBoundary(ExampleComponent);

Alternatively, You can also create an ErrorBoundary component but using HOC is much simpler as compared to the ErrorBoundary component.

Cons

Error boundaries do not catch errors for,

  1. Event handlers
  2. Async code (e.g. setTimeout or requestAnimationFrame callbacks)
  3. Server-side rendering
  4. Errors are thrown in the error boundary itself

At this point you'll need to wrap error-prone code to try-catch block and throw Exceprion from catch block which will be further handled by outer HOC. Read more at official docs

Related