Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React ErrorBoundary - Just can't get it to work

Apologies, I know this has been asked, but I can't find a single example that works for me. Seems like it's not easy enough to understand if so many are struggling with it.

I just need to know how to catch errors in React in a clean, simple way. I'm using CRA 2.0/React 16.7. I want a try/catch block at the action level, as this is where business logic is concentrated in my app.

I read this and implemented it as described, but my ErrorBoundary object never catches errors.

Example:

import React, { Component } from "react";

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

    static getDerivedStateFromError(error) {
        return { hasError: true };
    }

    componentDidCatch(error, info) {
        console.log("ErrorBoundary: ", error);
        console.log("ErrorBoundary: ", info);
    }

    render() {
        if (this.state.hasError) {
            return <h1>Something went wrong.</h1>;
        }
        return this.props.children;
    }
}

export default ErrorBoundary;

I'm wrapping my routes at the top level:

    return (
        <BrowserRouter>
            <ErrorBoundary>
                <div className="main-container">
                    <SideBar />
                    <div className="some-class">
                        <Switch>
                            <Route path="/events" component={Events} />
                            <Route exact path="/" component={Index} />
                        </Switch>
                    </div>
                </div>
            </ErrorBoundary>
        </BrowserRouter>
    );

In "events" above, I make an API call which is throwing a 500 error on the API side:

componentDidMount() {
    this.props.getAllEvents();
}

Here's the Redux action:

export const getAllEvents = () => {
    return async (dispatch, getState) => {
        try {
            let state = getState();
            const result = await get({
                state: state,
                route: "/v1/events"
            });
            dispatch({
                type: GET_EVENTS,
                payload: result
            });
        } catch (e) {
            console.log("Something went wrong at action...");
        }
    };
};

..."get()" is just wrapping an Axios GET - nothing fancy.

I see a 500 error in the console from the failed API call. I never see the "Something went wrong..." in the console, from the catch block above, though that line does get hit while debugging.

The "componentDidCatch()" method never gets called - "hasError" is always false and it always renders the children.

If I remove the throw block in the API endpoint, everything works fine and I get my data. I'm just not able to catch errors at the UI level. I've tried a try/catch in the "componentDidMount()", I've tried removing the try/catch block in the action...behavior doesn't change.

Thanks in advance.

like image 726
Tsar Bomba Avatar asked Oct 16 '22 06:10

Tsar Bomba


1 Answers

As the other answer correctly stated, React error boundary will only catch rendering errors.

Since you are using redux, you could build your own mechanism for cases like yours:

  1. Whenever an error occurs, you dispatch an action with an error property.
  2. A reducer only looks for actions that has error property. It receives the action and update a "global error" state in your state tree
  3. An app-wide connected wrapper component sees this change in state and displays fallback UI.

For example:
Dispatch an action with error property:

export const getAllEvents = () => {
    return async (dispatch, getState) => {
        try {
            ...
        } catch (e) {
            dispatch({
                type: ERROR, 
                error: e // dispatch an action that has `error` property
            });
        }
    };
};

A reducer sees it and update the state.error part:

export default function errorReducer(state = null, action) {
  const { type, error } = action;
  if (type === RESET_ERROR_MESSAGE) {  // an action to clear the error
    return null;
  } else if (error) { // any type of action, but contains an `error`
    return error;
  }
  return state;
}

Now you wrap your app in a connected boundary:

function Wrapper({error}) {
  if(error) return <h1>Something went wrong</h1>;
  return <App/>;
}

export default connect(
  state => ({
    error: state.error,
  })
)(Wrapper);
like image 113
mehamasum Avatar answered Nov 03 '22 08:11

mehamasum