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.
Am I going crazy? Have I totally misunderstood what React Error Boundaries are for?
Could something higher up the call stack be interfering with the error handler somehow?
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
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.
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.
Error Boundaries are only designed for intercepting errors that originate from 3 places in a React component: During render phase. In a lifecycle method.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With