React: Redirect to an error page with error message/stack trace using React Router

Viewed 4208

In my app if any errors occur inside components I need to redirect to a top-level error page. Suppose I'm using React-Router and have the following route defined,

<Switch>
   ..
   <Route path="/error">
      <Error />
   </Route>

Suppose in some down-level component something fails. I get an error object with .message and .stack. I need my Error component to display the error details after a Router <Redirect />.

const [error, setError] = useState({});

const someComponentFetch = async () => {
    const url = '/myapp/data';
    try {
         //...
    }
    catch (error) { 
        setError(error);
    }
}  

if (error.message) {
    return <Redirect to="/error" />
}

return (..);

Error component expects a props object:

export default function Error(props) {
    return (
        <div>
            Error Occurred: 
            <div>
                Message: {props.error.message} <br/>
                Stack: {props.error.stack}
            </div>
        </div>
    );
}

Error: TypeError: props.error is undefined . So <Redirect> doesn't pass my properties. What's the best way to have a top-level Error page with error details? If I were to check error ? .. in every component it wouldn't be a top-level page, it would be a message inside each component.

2 Answers

You can define your own represtation like this,

import React from 'react'

class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false, error: '', info: '', stack: '' }
    }
  
    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)
      this.setState({error, info: errorInfo, stack: error.stack})
    }
  
    render() {
      if (this.state.hasError) {
        // You can render any custom fallback UI
        return <h4>Something went wrong!</h4>;
      }
  
      return this.props.children; 
    }
}

export default ErrorBoundary

You can render error, info, and stack as per your need. You can use 'react-json-view' package to render the error and stack nicely.

And wrap your App with your error boundary,

<ErrorBoundary>
   <App />
</ErrorBoundary>

We just ended up redirecting to /error?... with a custom parameter string that tells the Error component what to display. When setting the Error object, we set all the data items we want to capture.

const [errorObj, setErrorObj] = useState({});

From Ajax, where errors can possibly get set:

catch (error) { 
    // We capture 2 data items for an error in a custom object: message and location.
    // Any other data items can also be in this custom object.
    setErrorObj({message: error.message, location: 'approvals.js: fetchTeleworkInfo'});
}

On render:

    errorObj.message
    ?
        <Redirect to={generatePath('./error?msg=:errorMessage', 
                                   {errorMessage: formatError(errorObj)})} />
    :
        {/* Continue with normal render */}
        <div>...</div>

The utility function formatError will produce some encoded string from the Error Object:

// Format error object into a string (expects errorObj.message and errorObj.location fields)
// Error string is encoded (and decoded in the Error component) to support URL passing
export function formatError(errorObj) {
    let result = '';
    if (errorObj) {
        if (errorObj.message) {
            result += errorObj.message;
        } 
        if (errorObj.location) {
            result += ' (' + errorObj.location + ')';
        }
    }
    return encodeURIComponent(result);
}

The Error component can work either from a prop or a URL. This is an example of how it reads the URL param:

export default function Error(props) {

    // Obtain URL Params (optional)
    // ----------------------------
    const queryStr = new URLSearchParams(useLocation().search); 

    let message = null;
    if (props.msg) {    // Error string from property
       message = props.msg;
    } else if (queryStr.get("msg")) {   // Error string from React-Router URL param
       message = queryStr.get("msg");
    }
    // Decode string since it always comes in encoded
    if (message) {
       message = decodeURIComponent(message);
    }

Then the Error component can render/format errors and check the message string as it wants, e.g.

    {message &&     
        <div>..</div>
    }
Related