2

The project I am working on needs better error handling and to begin I've decided to implement reacts ErrorBoundary hook componentDidCatch which I was able to implement simply in a single component. However a senior developer has recommended I make my error boundary a Higher Order Component so that I can wrap the entire application in it. This is where I am running into trouble because despite reading the documentation higher order components make little sense to me.

This is what I have implemented so far:

my HOC

import React from "react";
import ErrorScreen from "./ErrorScreen"


export default function NewErrorHandler(WrappedComponent) {
 class ErrorHandler extends React.Component {
    constructor(props) {
        this.state = {hasError: false}
    };

    componentDidCatch(error, info) {
        this.setState({ hasError: true })
    }

    render() {
        if(this.state.hasError) {
            return (
                <ErrorScreen/> 
            )
        } else {
            return this.props.children
        }
    }
}
}

My issue so far is Im not exactly sure how to wrap the application in the error boundary. In all the examples Ive seen the HOC's are wrapping around functional components easily through exporting however the way this application is set up there is no explicit exported function that I can see:

import ReactDOM from "react-dom";
import React from "react";
import { store, history } from "./store";
import { Provider } from "react-redux";
import { Route, Switch } from "react-router"; // react-router v4
import { ConnectedRouter } from "connected-react-router";
import DetectAdblock from "./components/DetectAdblock";
import ErrorBound from "./components/ErrorHOC";

const Encounter = Loadable({
  loader: () => import("./components/Encounter/Encounter"),
  loading: Loading,
  delay: 300
});

const VerifyInsuranceList = Loadable({
  loader: () => import("./components/VerifyInsurance/InsuranceList"),
  loading: Loading,
  delay: 300
});

const VideoChat = Loadable({
  loader: () => import("./components/VideoChat"),
  loading: Loading,
  delay: 300
});

document.addEventListener("DOMContentLoaded", () => {
  ReactDOM.render(
    
<Provider store={store}>
        <ConnectedRouter history={history}>
          <TokenLoader>
            <DetectAdblock />
            <BrowserBanner />
            <EnvBanner />
            <IdleMonitor />
            <TokenRefresher />
            <MonotonicClock frequency={15} />
            <RtcMonitor />
            <PatientPoller pollInterval={30000} />
            <Redirect />
            <Switch>
              <Route exact={true} path="/" component={ProviderDashboard} />
              <Route
                exact={true}
                path="/accept-invitation/:inviteID"
                component={AcceptInvite}
              />
              <Route exact={true} path="/login" component={Login} />
              <Route
                exact={true}
                path="/reset-password/:resetID"
                component={ResetPassword}
              />
              <Route
                exact={true}
                path="/request-password-reset"
                component={ForgotPassword}
              />
              <Route
                exact={true}
                path="/waiting-room"
                component={ProviderAvailablePatients}
              />
              <Route exact={true} path="/encounter" component={Encounter} />
              <Route exact={true} path="/video" component={VideoChat} />
              <Route
                exact={true}
                path="/providers"
                component={ManagerProviders}
              />
              <Route exact={true} path="/providers/new" component={Invite} />
              <Route
                exact={true}
                path="/providers/edit/:providerID"
                component={ProviderEdit}
              />
              <Route
                exact={true}
                path="/providers/audit/:providerID"
                component={ProviderAudit}
              />
              <Route
                exact={true}
                path="/activity-monitor"
                component={ActivitySummary}
              />
              <Route
                exact={true}
                path="/encounter-monitor"
                component={EncounterMonitor}
              />
              <Route
                exact={true}
                path="/encounter-monitor/:encounterID"
                component={EncounterMonitorDetails}
              />
              <Route exact={true} path="/billing" component={BillingTab} />
              <Route exact={true} path="/patients" component={PatientTab} />
              <Route exact={true} path="/rounding" component={RoundingTab} />
              <Route
                exact={true}
                path="/patients/:patientID"
                component={PatientChart}
              />
              <Route
                exact={true}
                path="/active-patient-chart/:patientID"
                component={ActivePatientChart}
              />
              <Route
                exact={true}
                path="/insurnace-history/:patientID"
                component={InsuranceHistory}
              />
              <Route
                exact={true}
                path="/verify-insurance"
                component={VerifyInsuranceList}
              />
              <Route render={() => <div>Not Found</div>} />
            </Switch>
          </TokenLoader>
        </ConnectedRouter>
      </Provider>
document.getElementById("app")
  );
});

Here I have left out some of the import statements but have shown how I am importing my ErrorHOC.js. Any insight into how to wrap the whole application would be super helpful. If I am missing information here needed for understanding please let me know.

CourtneyJ
  • 458
  • 6
  • 19
  • HOC is a function accepting a component, see docs: https://reactjs.org/docs/higher-order-components.html#gatsby-focus-wrapper, you just implemented a class component. You need to show some effort in SO questions. – Dennis Vash Jan 04 '21 at 18:07
  • Thats the part that I dont understand. Ive read the documentation and watched videos but I dont understand how I can ` 'take a component and return a new component' ` if you could just explain what the initial component would be and what the new component would be that would be great. Its the bigger picture that I dont understand. Like okay do I pass the "component" that is my index.js as shown above into my HOC component then return my errorScreen component? – CourtneyJ Jan 04 '21 at 18:10
  • 1
    Ill add an example tommorow, either way its just NOT a use case for HOC. I would implement a wrapper instead, also I NEVER seen such a use case and would like to hear your senior’s reasonings – Dennis Vash Jan 04 '21 at 20:36
  • Ah okay, yes after looking into it more and more I think you are correct it is a bad use case for an HOC. I think I will look into implementing a wrapper instead. Thanks for your feedback and anything else you share would be awesome to learn from. – CourtneyJ Jan 04 '21 at 21:10

1 Answers1

2

As discussed in the comments, error boundary is NOT a use case for HOC, any way here is a possible example of how the logic should work:

// withErrorBoundary.js
class ErrorHandler extends React.Component {}

// HOC wrapping the passed component
export default function withErrorBoundary(WrappedComponent) {
  const Component = (
    <ErrorHandler>
      <WrappedComponent />
    </ErrorHandler>
  );
  return Component;
}

// index.js
import withErrorBoundary from "./withErrorBoundary.js";

const App = <Provider store={store}>...</Provider>;

// Usage
const AppWithErrorBoundary = withErrorBoundary(App);

ReactDOM.render(<AppWithErrorBoundary />, document.getElementById("app"));

Error boundary should be a wrapper component so you can pass helpful props to it, easier reuse case on multiple usages, and more:

class ErrorBoundaryWrapper extends React.Component {
  render() {
    return (
      <>
        {/** Use other props passed to wrapper **/}
        <div>...</div>
        {this.props.children}
      </>
    );
  }
}

// Usage, see the advantage over HOC
<>
  <ErrorBoundaryWrapper specialProps={props1}>
    <Component1 />
  </ErrorBoundaryWrapper>
  <ErrorBoundaryWrapper specialProps={props2}>
    <Component2 />
  </ErrorBoundaryWrapper>
</>
Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • You can pass props to a HOC as well. I'd say `export const Pagination = withComponentErrorBoundary( memo(({ currentPage, totalPages, onPageChange }) => {` this is quite a nice pattern. – Frexuz Jun 29 '22 at 14:37