SoFunction
Updated on 2025-03-09

How React gracefully catches exceptions

Preface

No one is perfect, so the code always goes wrong. It is not scary to make mistakes, the key is how to deal with it.
I just want to ask you how to catch the errors of react application? At this time:

  • Xiaobai++: How to deal with it?
  • Xiaobai++: ErrorBoundary
  • Xiaobai+: ErrorBoundary, try catch
  • Xiao Hei#: ErrorBoundary, try catch,
  • Xiao Hei##: This is a serious question. I know N ways to deal with it. What better way do you have?

ErrorBoundary

EerrorBoundary is from the 16th version. Someone asked me what my 15th version is. I don’t listen, I don’t listen. Anyway, I use 16, of course 15 hasunstable_handleError

The introduction to the ErrorBoundary official website is quite detailed. This is not the point, the point is what exceptions it can catch.

  • Rendering of subcomponents
  • Lifecycle function
  • Constructor
  • class ErrorBoundary extends {
  constructor(props) {
    super(props);
     = { hasError: false };
  }

  componentDidCatch(error, info) {
    // Display fallback UI
    ({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

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

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

The open source world is good, it has been encapsulated by a great masterreact-error-boundary This excellent library.
You just need to care about what you need to care about after an error occurs, and you can also get a Reset, perfect.

import {ErrorBoundary} from 'react-error-boundary'

function ErrorFallback({error, resetErrorBoundary}) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  )
}

const ui = (
  <ErrorBoundary
    FallbackComponent={ErrorFallback}
    onReset={() => {
      // reset the state of your app so the error doesn't happen again
    }}
  >
    <ComponentThatMayError />
  </ErrorBoundary>
)

Unfortunately, error boundaries do not catch these errors:

  • Event handler
  • Asynchronous code (.setTimeout or requestAnimationFrame callbacks)
  • Rendering code on the server
  • error boundaries thrown by themselves

The original text can be found in the official websiteintroducing-error-boundaries

What this article wants to capture is the event handler error.
The official actually has a planhow-about-event-handlers, that is try catch.

But, with so many event handlers, my god, how much do I have to write,. . . . . . . . . . . . . . . . . . . .

  handleClick() {
    try {
      // Do something that could throw
    } catch (error) {
      ({ error });
    }
  }

Outside of Error Boundary

Let's first look at a table, which lists the means and scope of our ability to catch exceptions.

Exception type Synchronization method Asynchronous Method Resource loading Promise async/await
try/catch
error
unhandledrejection

try/catch

Synchronous and async/await exceptions can be caught.

, error event

    ('error', , true);
     = 

('error') This can record exceptions more than capture resources.
Please note that the last parameter is true, if false, it may not be as good as you expect.
Of course, if you have the meaning of the third parameter, I would be a little unwilling to pay attention to you. bye.

unhandledrejection

Note that the last parameter is true.

('unhandledrejection', , true)

It catches exceptions for uncatched Promise.

XMLHttpRequest and fetch

XMLHttpRequest is easy to handle, and you have oneerror events.
Of course, you will not encapsulate a library based on XMLHttpRequest by 99.99%. Axios is really good, and there is a complete error handling mechanism.
As for fetch, if you take the catch yourself, it will be your own problem if you don’t deal with it.
So many, it's too difficult.
Fortunately, there is actually a libraryreact-error-catch It is a component encapsulated based on ErrorBoudary, error and unhandledrejection.
Its core is as follows

    = function () {
        // event catch
        ('error', , true);
        // async code
        ('unhandledrejection', , true);
    };

use:

import ErrorCatch from 'react-error-catch'

const App = () =&gt; {
  return (
  &lt;ErrorCatch
      app="react-catch"
      user="cxyuns"
      delay={5000}
      max={1}
      filters={[]}
      onCatch={(errors) =&gt; {
        ('It's an error');
        // Report exception information to the backend and create tags dynamically        new Image().src = `http://localhost:3000/log/report?info=${(errors)}`
      }}
    &gt;
      &lt;Main /&gt;
    &lt;/ErrorCatch&gt;)
}

export default

Applause, applause.
In fact, this is not the case: The most important thing about errors captured using error is to provide error stack information, which is quite unfriendly to analysis errors, especially after packaging.
There are so many errors, so I will first handle the event handler in React.
As for the others, to be continued.

Exception capture of event handler

Example

My idea is very simple,decoratorRewrite the original method.
Let's take a look at the use:

   @methodCatch({ message: "Create order failed", toast: true, report:true, log:true })
    async createOrder() {
        const data = {...};
        const res = await createOrder();
        if (!res ||  !== 0) {
            return ("Create order failed");
        }
        
        .......
        Other codes that may generate exceptions
        .......
        
       ("Order creation succeeded");
    }

Note four parameters:

  • message: Printed error when an error occurs
  • toast: An error occurred, whether Toast
  • report: An error occurred, whether to report it
  • log: Use Print

Maybe you say that the news will definitely die, it is unreasonable. What if I have other news.
At this moment, I smiled slightly and don't worry, look at another piece of code

  @methodCatch({ message: "Create order failed", toast: true, report:true, log:true })
    async createOrder() {
        const data = {...};
        const res = await createOrder();
        if (!res ||  !== 0) {
            return ("Create order failed");
        }
       
        .......
        Other codes that may generate exceptions
        .......
        
       throw new CatchError("Creating an order failed, please contact the administrator", {
           toast: true,
           report: true,
           log: false
       })
       
       ("Order creation succeeded");

    }

Yes, yes, you can override the previous default option by throwing a custom CatchError.
This methodCatch can catch, synchronous and asynchronous errors, let's take a look at the entire code together.

Type definition

export interface CatchOptions {
    report?: boolean;
    message?: string;
    log?: boolean;
    toast?: boolean;
}

// It is more reasonable to write hereexport const DEFAULT_ERROR_CATCH_OPTIONS: CatchOptions = {
    report: true,
    message: "Unknown exception",
    log: true,
    toast: false
}

Custom CatchError

import { CatchOptions, DEFAULT_ERROR_CATCH_OPTIONS } from "@typess/errorCatch";

export class CatchError extends Error {

    public __type__ = "__CATCH_ERROR__";
    /**
      *Catched errors
      * @param message message
      * @options Other parameters
      */
    constructor(message: string, public options: CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) {
        super(message);
    }
}

Decorators

import Toast from "@components/Toast";
import { CatchOptions, DEFAULT_ERROR_CATCH_OPTIONS } from "@typess/errorCatch";
import { CatchError } from "@util/error/CatchError";


const W_TYPES = ["string", "object"];
export function methodCatch(options: string | CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) {

    const type = typeof options;

    let opt: CatchOptions;

    
    if (options == null || !W_TYPES.includes(type)) { // null or not a string or object        opt = DEFAULT_ERROR_CATCH_OPTIONS;
    } else if (typeof options === "string") {  // String        opt = {
            ...DEFAULT_ERROR_CATCH_OPTIONS,
            message: options || DEFAULT_ERROR_CATCH_OPTIONS.message,
        }
    } else { // Valid object        opt = { ...DEFAULT_ERROR_CATCH_OPTIONS, ...options }
    }

    return function (_target: any, _name: string, descriptor: PropertyDescriptor): any {

        const oldFn = ;

        (descriptor, "value", {
            get() {
                async function proxy(...args: any[]) {
                    try {
                        const res = await (this, args);
                        return res;
                    } catch (err) {
                        // if (err instanceof CatchError) {
                        if(err.__type__ == "__CATCH_ERROR__"){
                            err = err as CatchError;
                            const mOpt = { ...opt, ...( || {}) };

                            if () {
                                ("asyncMethodCatch:",  ||  , err);
                            }

                            if () {
                                // TODO::
                            }

                            if () {
                                ();
                            }

                        } else {
                            
                            const message =  || ;
                            ("asyncMethodCatch:", message, err);

                            if () {
                                (message);
                            }
                        }
                    }
                }
                proxy._bound = true;
                return proxy;
            }
        })
        return descriptor;
    }
}

Let's summarize

Rewrite the original method using the decorator to achieve the purpose of catching errors
Customize the error class and throw it to achieve the purpose of overwriting the default options. Added flexibility.

  @methodCatch({ message: "Create order failed", toast: true, report:true, log:true })
    async createOrder() {
        const data = {...};
        const res = await createOrder();
        if (!res ||  !== 0) {
            return ("Create order failed");
        }
       ("Order creation succeeded");
       
        .......
        Other codes that may generate exceptions
        .......
        
       throw new CatchError("Creating an order failed, please contact the administrator", {
           toast: true,
           report: true,
           log: false
       })
    }

Next step

What's next? Take one step at a time.
No, the road ahead is still long. This is a basic version.

Expand the results

@XXXCatch
classs AAA{
    @YYYCatch
    method = ()=> {
    }
}

Abstract, abstract, abstract

goodbye.

Written at the end

error-boundaries
React exception handling
catching-react-errors
react advanced exception handling mechanism - error Boundaries
decorator
core-decorators

This is the end of this article about how React gracefully catches exceptions. For more related React to catch exceptions, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!