I faced a weird edge case with React ErrorBoundary. Exceptions are not being processed if they happen in inline expression. For example:
const page = webtexts[courseId].pages[pageFamilyId]; // Returns undefined
...
<ErrorBoundary>
// "Cannot read properties of undefined (reading 'page_name')" won't be processed in ErrorBoundary
<span>{page.page_name}</span>
</ErrorBoundary>
But if I wrap a problematic place in the component, it works properly.
const page = webtexts[courseId].pages[pageFamilyId]; // Returns undefined
...
const WrapFailingPage: React.FC<{ page: any }> = ({ page }) => {
return <span>{page.page_name}</span>;
};
...
<ErrorBoundary>
<WrapFailingPage page={page} /> // The error is properly processed in ErrorBoundary
</ErrorBoundary>
For the demo, I made a simple Codepen - https://codepen.io/OlexanderD/pen/wvpdpxY.
Should the ErrorBoundary
behave in that way? Why the inline expressions can be problematic?
ErrorBoundary
tracks only the code parts that were created with React.createElement(React.Component / React.[V]FC)
. Therefore, if you return a broken JSX from a custom React component, ErrorBoundary
will pick it up and process.
A bit of terminology:
element
- any HTML tag. It'll be processed as React.createElement('div', ...)
component
- any React component, not native HTML tag. I'll be processed as React.createElement(MyComponent, ...)
According to the docs, ErrorBoundary
can process exceptions only in their child component tree, but I tried to catch an exception from the element.
React.createElement() can receives 2 types of arguments: tag name (div
, span
, ...) or React component. Therefore, when we simply use <div>{fail.absurdIntentionalError}</div>
and pass it to parent div
the React will generate this code:
React.createElement(
"div",
null,
React.createElement("button", ...),
showFailingChild &&
React.createElement("div", null, fail.absurdIntentionalError)
);
As you can see we're passing an element as the part of the props.children
for the parent div
. Therefore the ErrorBoundary
won't process it as the part of the component tree.
But if we use some wrapper like FailingComponent
, the generated JSX will look like this:
React.createElement(
"div",
null,
React.createElement("button", ...),
showFailingComponent &&
React.createElement(FailingComponent, null)
);
Now we're passing the component to the props.children
for the parent div
. And the ErrorBoundary
will process the result of the FailingComponent
as the part of the component tree.