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}