diff --git a/plugins/ui/src/js/src/DashboardPlugin.tsx b/plugins/ui/src/js/src/DashboardPlugin.tsx
index 0b22fc62e..614dfe17f 100644
--- a/plugins/ui/src/js/src/DashboardPlugin.tsx
+++ b/plugins/ui/src/js/src/DashboardPlugin.tsx
@@ -76,13 +76,7 @@ export function DashboardPlugin(
>(new Map());
const handleWidgetOpen = useCallback(
- ({
- widgetId = nanoid(),
- widget,
- }: {
- widgetId: string;
- widget: WidgetDescriptor;
- }) => {
+ ({ widgetId, widget }: { widgetId: string; widget: WidgetDescriptor }) => {
log.debug('Opening widget with ID', widgetId, widget);
setWidgetMap(prevWidgetMap => {
const newWidgetMap = new Map(prevWidgetMap);
diff --git a/plugins/ui/src/js/src/layout/ReactPanel.tsx b/plugins/ui/src/js/src/layout/ReactPanel.tsx
index 6ff46f70f..9ebdc1180 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,
+ LoadingOverlay,
+} from '@deephaven/components';
import Log from '@deephaven/log';
import PortalPanel from './PortalPanel';
import { ReactPanelControl, useReactPanel } from './ReactPanelManager';
@@ -186,6 +192,20 @@ function ReactPanel({
);
const widgetStatus = useWidgetStatus();
+ let renderedChildren: React.ReactNode;
+ if (widgetStatus.status === 'loading') {
+ renderedChildren = (
+ <>
+
+ {children}
+ >
+ );
+ } else if (widgetStatus.status === 'error') {
+ renderedChildren = ;
+ } else {
+ renderedChildren = children;
+ }
+
return portal
? ReactDOM.createPortal(
@@ -224,11 +244,7 @@ function ReactPanel({
* Don't render the children if there's an error with the widget. If there's an error with the widget, we can assume the children won't render properly,
* but we still want the panels to appear so things don't disappear/jump around.
*/}
- {widgetStatus.status === 'error' ? (
-
- ) : (
- children
- )}
+ {renderedChildren}
diff --git a/plugins/ui/src/js/src/widget/WidgetHandler.tsx b/plugins/ui/src/js/src/widget/WidgetHandler.tsx
index e4184cfd1..121d9f666 100644
--- a/plugins/ui/src/js/src/widget/WidgetHandler.tsx
+++ b/plugins/ui/src/js/src/widget/WidgetHandler.tsx
@@ -64,6 +64,14 @@ function WidgetHandler({
initialData: initialDataProp,
}: WidgetHandlerProps): JSX.Element | null {
const { widget, error: widgetError } = useWidget(widgetDescriptor);
+ const [isLoading, setIsLoading] = useState(true);
+ const [prevWidget, setPrevWidget] = useState(widget);
+ // Cannot use usePrevious to change setIsLoading
+ // Since usePrevious runs in an effect, the value never gets updated if setIsLoading is called during render
+ if (widget !== prevWidget) {
+ setPrevWidget(widget);
+ setIsLoading(true);
+ }
const [document, setDocument] = useState();
@@ -226,6 +234,7 @@ function WidgetHandler({
const newDocument = parseDocument(documentParam);
setInternalError(undefined);
setDocument(newDocument);
+ setIsLoading(false); // Must go after setDocument since setters are not batched in effects
if (stateParam != null) {
try {
const newState = JSON.parse(stateParam);
@@ -339,11 +348,14 @@ function WidgetHandler({
if (error != null) {
return { status: 'error', descriptor: widgetDescriptor, error };
}
+ if (isLoading) {
+ return { status: 'loading', descriptor: widgetDescriptor };
+ }
if (renderedDocument != null) {
return { status: 'ready', descriptor: widgetDescriptor };
}
return { status: 'loading', descriptor: widgetDescriptor };
- }, [error, renderedDocument, widgetDescriptor]);
+ }, [error, renderedDocument, widgetDescriptor, isLoading]);
return useMemo(
() =>