قالب وردپرس درنا توس
Home / Tips and Tricks / How to use React’s error boundaries to absorb crashes – CloudSavvy IT

How to use React’s error boundaries to absorb crashes – CloudSavvy IT



Comment logo on dark background

React error boundaries allow you to catch JavaScript errors that occur in child components. Any unhandled error that is below the boundary tree position will be caught, preventing a crash.

You can display your own fallback UI after an error boundary catches an error. This allows you to communicate the problem to the user in an elegant way. They can continue to use the rest of your interface without getting a completely crashed tab.

Create Error Limits

Any component of the React class can become an error boundary. You just need to set one of the following lifecycle methods:

  • componentDidCatch(error) – This instance method is called when the component detects an error. You can use this to report the error to an analysis or monitoring service.
  • static getDerivedStateFromError(error) – This static method can be used to update the state of your component after an error has occurred. This is how you display a fallback UI.

Here̵

7;s what the two methods look like in use:

componentDidCatch()

class MyComponent extends React.Component {
 
    componentDidCatch(error) {
        // use custom reporting framework
        logErrorToAnalytics(error);
        this.props.onError(error);
    }
 
}

Using componentDidCatch(), your component can report the error in any way it sees fit. Since it is an instance method, you can also pass it through props in the component tree.

getDerivedStateFromError(error)

class MyComponent extends React.Component {
 
    state = {error: null};
 
    render() {
        return <h1>{!this.state.error ? "Hello" : "Error"}h1>;
    }
 
    static getDerivedStateFromError(error) {
        return {error};
    }
 
}

getDerivedStateFromError() also receives the JavaScript error object. It should return an object that describes the state transformation to apply to your component.

React will pass the returned object to: setState(). In this example, the value of the error key in the state of the component is set to the trapped error object. This will cause the displayed output to change to: Error instead of the default Hello text.

Which method to use?

If the two boundary methods are similar, it’s because they are! Technically, you can define one or both of these methods and still have the same results: componentDidCatch() could call setState() to update the status of your component, and getDerivedStateFromError() could call a third-party auditing service to report errors it records.

The difference is in the phase in which the error is received. componentDidCatch() catches errors in the commit phase, after React updates the DOM. getDerivedStateFromError() is called during the render phase, before React updates the browser’s DOM.

This subtlety of timing explains why getDerivedStateFromError() is generally used to switch to the fallback UI. When a serious error occurs, updating the DOM can cause further errors if your app was left in an inconsistent state. Updating the state before the DOM update took place causes the fallback UI to appear immediately.

Locating Your Error Boundaries

You are free to use error limits wherever you want. It is good practice to use multiple error boundaries. Add an error boundary for each main layer of your user interface. This allows you to isolate errors in your page content from the application shell so that a crash in a route doesn’t disable your navigation bar.

Here’s a simple component hierarchy:

export const () => (
    <App>
        <Header />
        <Router />
        <Footer />
    App>
);

In this application, the App component is a simple wrapper that manages top-level state. Header displays a navigation bar and Footer displays the bottom bar. The content of the main page – where crashes are most common – is loaded dynamically by Router, based on the current URL.

By default, a crash is within the Router kids are hitting the whole site. By placing an error boundary around Router, errors that occur within the component can be handled gracefully. The header and footer remain usable while the main page content is replaced with a fallback message.

The app needs at least one more error limit. The children of packing App ensures that errors in the header or footer can be caught. In this situation, it may be acceptable to replace the entire user interface with a full-page error message.

Here is the refactored component structure:

class ErrorBoundary extends React.Component {
 
    state = {error: null};
 
    render() {
        if (!this.state.error) return this.props.children;
        else return <h1>Error!h1>;
    }
 
    static getDerivedStateFromError(error) {
        return {error};
    }
 
}
 
export const () => (
    <App>
        <ErrorBoundary>
            <Header>
            <ErrorBoundary>
                <Router />
            ErrorBoundary>
            <Footer />
        ErrorBoundary>
    App>
);

We’ve abstracted the error boundary logic into a reusable component. We can pack now ErrorBoundary around all components to be isolated from their parents. Remember, you don’t need to create an error boundary component – for simple applications or a specific UI component, you can add the lifecycle brackets directly to a component class.

Limitations of Error Limits

Error limits have some important limitations that you should be aware of. They are capable of detecting most unhandled JavaScript errors, but some will go unnoticed.

Error boundaries do not intercept errors that occur in event handling methods. Event handler code does not affect the React rendering process, so the framework can still render your components. Because event handler errors do not result in a corrupted user interface or decoupling components, React does not attempt to intercept them.

If you need to respond to errors in your event handlers, use a normal try/catch block. Perform a status update in the catch instruction to put your user interface in an error state.

class MyComponent extends React.Component {
 
    state = {error: null};
 
    handleClick = () => {
        try {
            doSomething();
        }
        catch (error) {
            this.setState({error});
        }
    }
 
    render() {
        if (this.state.error) return <p>Error!p>;
        else return <button onClick={this.handleClick}>Submitbutton>
    }
 
}

Other than event handlers, error boundaries cannot detect errors that occur in asynchronous code. If you use Promises, async/await, or setTimeout(), make sure you try/catch/Promise.catch() blocks to catch any errors.

A common misunderstanding about error boundaries concerns the tree they are guarding. They can nothing but catch errors that occur deeper in the tree. Error boundaries do not capture errors caused by the boundary component itself.

export default () => (
    <App>                   // Errors won't be caught
        <ErrorBoundary>     // Errors won't be caught
            <Router />      // Errors thrown here will be caught
        ErrorBoundary>
    App>
);

Each error boundary must wrap around the components that can cause an error.

Finally, only class-based components can be error bounds. There is currently no mechanism to allow a functional component to become an error boundary. If you’re working in a functional codebase, you’ll need to create a reusable error boundary component as shown above. You can then wrap your components with it whenever you need a fault boundary.

Conclusion

Error Boundaries Bring JavaScripts try/catch to React’s declarative rendering model. This allows you to isolate parts of your site’s user interface so that a crash in one component doesn’t affect its siblings.

You need to evaluate your app’s user interface to identify the critical sections. Place error boundaries strategically to prevent an unhandled error from decoupling your entire component structure. Users are much more accepting of a fully stylized “something went wrong” than a white screen that needs to be refreshed.


Source link