Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Error Boundary never working at all

I have a component which, within its render method, generates an error.

Higher up in the tree, the component is connect()ed to a resource using react-redux to fetch data, and I have other HOCS and so forth (injecting classes etc).

The component uses an error boundary in case (say) a property is missing from the resource supplied to it (common issue).

I've distilled minimal example, accessing a property of undefined to reproduce the same error:

import React from 'react'

// An Error Boundary which returns either a fallback component (if supplied to props) or (default) an empty div, instead of screwing up the whole tree
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      hasError: false,
    }
  }

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

  componentDidCatch(error, info) {
    this.setState({ hasError: true })
    console.log('do stuff with', error, info)
  }

  render() {
    console.log('RENDERING ERROR BOUNDARY WITH STATE:', this.state)
    if (this.state.hasError) {
      if (this.props.fallback) {
        return (this.props.fallback)
      }
      return (<div />)
    }
    return this.props.children
  }
}

class MyComponent extends React.Component {
  render() {
    const resource = undefined
    return (
      <ErrorBoundary>
        <p>{resource.missingProperty}</p>
      </ErrorBoundary>
    )
  }
}

export default MyComponent

Of course, attempting to render the component generates the following stack trace:

Uncaught TypeError: Cannot read property 'missingProperty' of undefined
    at MyComponent.render (index.jsx:58)
    at finishClassComponent (react-dom.development.js:15141)
    at updateClassComponent (react-dom.development.js:15096)
    at beginWork (react-dom.development.js:15980)
    at performUnitOfWork (react-dom.development.js:19102)
    at workLoop (react-dom.development.js:19143)
    at HTMLUnknownElement.callCallback (react-dom.development.js:147)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:196)
    at invokeGuardedCallback (react-dom.development.js:250)
    at replayUnitOfWork (react-dom.development.js:18350)
    at renderRoot (react-dom.development.js:19261)
    at performWorkOnRoot (react-dom.development.js:20165)
    at performWork (react-dom.development.js:20075)
    at performSyncWork (react-dom.development.js:20049)
    at requestWork (react-dom.development.js:19904)
    at scheduleWork (react-dom.development.js:19711)
    at Object.enqueueSetState (react-dom.development.js:12936)
    at Connect.push../node_modules/react/cjs/react.development.js.Component.setState (react.development.js:356)
    at Connect.onStateChange (connectAdvanced.js:205)
    at Object.notify (Subscription.js:23)
    at Subscription.notifyNestedSubs (Subscription.js:62)
    at Connect.onStateChange (connectAdvanced.js:202)
    at Object.notify (Subscription.js:23)
    at Subscription.notifyNestedSubs (Subscription.js:62)
    at Connect.onStateChange (connectAdvanced.js:202)
    at Object.dispatch (createStore.js:175)
    at e (<anonymous>:1:40553)
    at asyncDispatchMiddleware.js:33
    at middleware.js:13
    at middlewareCamel.js:13
    at middlewareDate.js:34
    at _callee$ (middleware.js:285)
    at tryCatch (runtime.js:63)
    at Generator.invoke [as _invoke] (runtime.js:290)
    at Generator.prototype.<computed> [as next] (runtime.js:116)
    at step (asyncToGenerator.js:21)
    at asyncToGenerator.js:32

The above error occurred in the <MyComponent> component:
    in MyComponent (created by Connect(MyComponent))
    in Connect(MyComponent) (at withCachedResource.js:43)
    in _class (created by StreamField)
    in div (at streamfield.js:17)
    in StreamField (at StoryDrawer.js:34)
    in div (created by Grid)
    in Grid (created by WithStyles(Grid))
    in WithStyles(Grid) (at GridItem.jsx:20)
    in GridItem (created by WithStyles(GridItem))
    in WithStyles(GridItem) (at StoryDrawer.js:33)
    in div (created by Grid)
    in Grid (created by WithStyles(Grid))
    in WithStyles(Grid) (at GridContainer.jsx:20)
    in GridContainer (created by WithStyles(GridContainer))
    in WithStyles(GridContainer) (at StoryDrawer.js:26)
    in StoryDrawer (created by WithStyles(StoryDrawer))
    in WithStyles(StoryDrawer) (created by Connect(WithStyles(StoryDrawer)))
    in Connect(WithStyles(StoryDrawer)) (at Map.js:264)
    in div (created by Paper)
    in Paper (created by WithStyles(Paper))
    in WithStyles(Paper) (created by Drawer)
    in Transition (created by Slide)
    in EventListener (created by Slide)
    in Slide (created by WithTheme(Slide))
    in WithTheme(Slide) (created by Drawer)
    in RootRef (created by Modal)
    in div (created by Modal)
    in Portal (created by Modal)
    in Modal (created by WithStyles(Modal))
    in WithStyles(Modal) (created by Drawer)
    in Drawer (created by WithStyles(Drawer))
    in WithStyles(Drawer) (at Map.js:252)
    in div (at Map.js:153)
    in Map (created by WithStyles(Map))
    in WithStyles(Map) (created by Connect(WithStyles(Map)))
    in Connect(WithStyles(Map)) (created by AddUrlProps(Connect(WithStyles(Map))))
    in AddUrlProps(Connect(WithStyles(Map))) (at MapPage.jsx:24)
    in div (at FullArea/index.js:43)
    in FullArea (at MapPage.jsx:22)
    in div (at MapPage.jsx:21)
    in MapPage (created by WithStyles(MapPage))
    in WithStyles(MapPage) (created by Connect(WithStyles(MapPage)))
    in Connect(WithStyles(MapPage)) (created by Route)
    in Route (at App.js:33)
    in Switch (at App.js:25)
    in div (at App.js:23)
    in App (at src/index.js:32)
    in RouterToUrlQuery (at src/index.js:31)
    in Router (created by ConnectedRouter)
    in ConnectedRouter (at src/index.js:30)
    in Provider (at src/index.js:29)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit ... to learn more about error boundaries.

What's SUPER weird is that the ErrorBoundary render() method never prints to the console. It's as if that wrapper just doesn't exist, and react completely ignores it, rendering only the children directly. I've no idea how that's even possible.

I've pushed this onto a production server, and the same appears there, so I don't think it's to do with development vs production configuration.

Questions

  1. Am I going crazy? Have I totally misunderstood what React Error Boundaries are for?

  2. Could something higher up the call stack be interfering with the error handler somehow?

Solution from @thedude

Show the accepted answer some love with your votes, folks. The corrected solution (clear now I see it) is that the component producing the error has to be wrapped, not simply the elements that the component defines. Corrected code:

import React from 'react'

class MyComponent extends React.Component {
  render() {
    const resource = undefined
    return (
      <p>{resource.missingProperty}</p>
    )
  }
}

class MyBoundedComponent extends React.Component {
  render() {
    return (
      <ErrorBoundary>
        <MyComponent />
      </ErrorBoundary>
    )
  }
}

export default MyBoundedComponent
like image 626
thclark Avatar asked May 21 '19 14:05

thclark


People also ask

How do you force error boundaries in React?

With the new feature in React, developers can test the Error Boundaries by a toggle error button, added to the DevTools. When we click on the toggle error button on the component labeled 'Inner Component', Error boundary is triggered.

How do you handle error boundaries in functional components?

In order to use Error Boundary in Functional Component, I use react-error-boundary. When we run this application, we will get a nice error display form the content of ErrorHandler component. React error boundary catches any error from the components below them in the tree.

How many error boundaries are in React?

Error Boundaries are only designed for intercepting errors that originate from 3 places in a React component: During render phase. In a lifecycle method.

What is the difference between try catch block and error boundaries?

catch deals with imperative code while error boundaries*deal with declarative code. Imperative programming is how you do something and declarative programming is what you do. With error boundary, if there is an error, you can trigger a fallback UI; whereas, with try… catch, you can catch errors in your code.


1 Answers

The ErrorBoundry is never rendered because the render method of MyComponent never returns.

The error occurs on every render.

render in react produces an representation of the element tree to be used by react, the error you caused short-circuits this process.

You can make your example work by wrapping the error boundary over MyComponent not inside it (in the component that renders MyComponent

like image 161
thedude Avatar answered Nov 15 '22 06:11

thedude