diff --git a/plugins/ui/src/js/src/layout/ReactPanel.test.tsx b/plugins/ui/src/js/src/layout/ReactPanel.test.tsx
index 793d29014..f776149cf 100644
--- a/plugins/ui/src/js/src/layout/ReactPanel.test.tsx
+++ b/plugins/ui/src/js/src/layout/ReactPanel.test.tsx
@@ -1,6 +1,7 @@
import React from 'react';
-import { render } from '@testing-library/react';
+import { render, within } from '@testing-library/react';
import { LayoutUtils, useListener } from '@deephaven/dashboard';
+import { TestUtils } from '@deephaven/utils';
import ReactPanel from './ReactPanel';
import {
ReactPanelManager,
@@ -260,3 +261,35 @@ it('calls setActiveContentItem if metadata changed while the panel already exist
expect(onClose).not.toHaveBeenCalled();
expect(mockStack.setActiveContentItem).toHaveBeenCalledTimes(1);
});
+
+it('catches an error thrown by children, renders error view', () => {
+ TestUtils.disableConsoleOutput();
+
+ const error = new Error('test error');
+ const ErrorComponent = () => {
+ throw error;
+ };
+
+ const portal = document.createElement('div');
+ const portals = new Map([[mockPanelId, portal]]);
+
+ const { rerender } = render(
+
+ {makeReactPanelManager({
+ children: ,
+ })}
+
+ );
+ const { getByText } = within(portal);
+ expect(getByText('Error: test error')).toBeDefined();
+
+ rerender(
+
+ {makeReactPanelManager({
+ children: Hello
,
+ })}
+
+ );
+
+ expect(getByText('Hello')).toBeDefined();
+});
diff --git a/plugins/ui/src/js/src/layout/ReactPanel.tsx b/plugins/ui/src/js/src/layout/ReactPanel.tsx
index 759453ba4..aefb2744f 100644
--- a/plugins/ui/src/js/src/layout/ReactPanel.tsx
+++ b/plugins/ui/src/js/src/layout/ReactPanel.tsx
@@ -7,7 +7,13 @@ import {
useLayoutManager,
useListener,
} from '@deephaven/dashboard';
-import { View, ViewProps, Flex, FlexProps } from '@deephaven/components';
+import {
+ View,
+ ViewProps,
+ Flex,
+ FlexProps,
+ ErrorBoundary,
+} from '@deephaven/components';
import Log from '@deephaven/log';
import PortalPanel from './PortalPanel';
import { ReactPanelControl, useReactPanel } from './ReactPanelManager';
@@ -94,6 +100,10 @@ function ReactPanel({
// eslint-disable-next-line react-hooks/exhaustive-deps
const contentKey = useMemo(() => shortid.generate(), [metadata]);
+ // We want to regenerate the error boundary key every time the children change, so that the error is cleared
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const errorKey = useMemo(() => shortid.generate(), [children]);
+
const parent = useParentItem();
const { eventHub } = layoutManager;
@@ -199,7 +209,8 @@ function ReactPanel({
rowGap={rowGap}
columnGap={columnGap}
>
- {children}
+ {/* Have an ErrorBoundary around the children to display an error in the panel if there's any errors thrown when rendering the children */}
+ {children}