Compare commits

...

2 Commits

Author SHA1 Message Date
Evan Rusackas
4374e7e9d7 fix(deps): use react-error-boundary package instead of reimplementing
Replace the reimplemented ErrorBoundary component with a wrapper around
the react-error-boundary package. The wrapper makes fallback props
optional (defaulting to null) to maintain backwards compatibility with
existing usage throughout Superset.

This:
- Reduces code duplication
- Leverages the well-maintained upstream package
- Adds useErrorBoundary and withErrorBoundary exports
- Maintains API compatibility (fallback props remain optional)

Pinned to version 6.0.0 for React 17 peer dependency compatibility.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 02:25:00 -08:00
Evan Rusackas
92d4e16de6 chore: consolidate ErrorBoundary to @superset-ui/core
This change:
- Creates a new ErrorBoundary in @superset-ui/core based on react-error-boundary API
- Removes the react-error-boundary npm dependency
- Updates all usages to import from @superset-ui/core
- Deletes the old src/components/ErrorBoundary directory

The new ErrorBoundary supports:
- FallbackComponent prop for custom error UI
- fallbackRender prop for render function fallback
- onError callback for error reporting
- resetErrorBoundary for error recovery

When no fallback is provided, errors are caught silently (returns null).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 13:50:30 -08:00
20 changed files with 138 additions and 185 deletions

View File

@@ -50000,12 +50000,15 @@
"license": "MIT"
},
"node_modules/react-error-boundary": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.0.tgz",
"integrity": "sha512-02k9WQ/mUhdbXir0tC1NiMesGzRPaCsJEWU/4bcFrbY1YMZOtHShtZP6zw0SJrBWA/31H0KT9/FgdL8+sPKgHA==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.0.0.tgz",
"integrity": "sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.12.5"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
"react": ">=16.13.1"
}
},
"node_modules/react-google-recaptcha": {
@@ -63712,7 +63715,7 @@
"re-resizable": "^6.11.2",
"react-ace": "^14.0.1",
"react-draggable": "^4.5.0",
"react-error-boundary": "^6.1.0",
"react-error-boundary": "6.0.0",
"react-js-cron": "^5.2.0",
"react-markdown": "^8.0.7",
"react-resize-detector": "^7.1.2",

View File

@@ -52,10 +52,10 @@
"react-ace": "^14.0.1",
"react-js-cron": "^5.2.0",
"react-draggable": "^4.5.0",
"react-error-boundary": "6.0.0",
"react-resize-detector": "^7.1.2",
"react-syntax-highlighter": "^16.1.0",
"react-ultimate-pagination": "^1.3.2",
"react-error-boundary": "^6.1.0",
"react-markdown": "^8.0.7",
"regenerator-runtime": "^0.14.1",
"rehype-raw": "^7.0.0",

View File

@@ -0,0 +1,107 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ReactNode, ComponentType, ErrorInfo } from 'react';
import {
ErrorBoundary as ReactErrorBoundary,
FallbackProps,
useErrorBoundary,
withErrorBoundary,
} from 'react-error-boundary';
/**
* Props for ErrorBoundary component.
* All fallback props are optional - defaults to rendering null on error.
*/
export interface ErrorBoundaryProps {
children: ReactNode;
FallbackComponent?: ComponentType<FallbackProps>;
fallbackRender?: (props: FallbackProps) => ReactNode;
fallback?: ReactNode;
onError?: (error: Error, info: ErrorInfo) => void;
onReset?: () => void;
}
/**
* Default fallback that renders nothing (preserves original Superset behavior).
*/
const defaultFallback = () => null;
/**
* ErrorBoundary wrapper that makes fallback optional.
* Uses react-error-boundary under the hood but allows usage without
* specifying a fallback (defaults to null, matching original behavior).
*/
export function ErrorBoundary({
children,
FallbackComponent,
fallbackRender,
fallback,
onError,
onReset,
}: ErrorBoundaryProps) {
if (FallbackComponent) {
return (
<ReactErrorBoundary
FallbackComponent={FallbackComponent}
onError={onError}
onReset={onReset}
>
{children}
</ReactErrorBoundary>
);
}
if (fallbackRender) {
return (
<ReactErrorBoundary
fallbackRender={fallbackRender}
onError={onError}
onReset={onReset}
>
{children}
</ReactErrorBoundary>
);
}
if (fallback !== undefined) {
return (
<ReactErrorBoundary
fallback={fallback}
onError={onError}
onReset={onReset}
>
{children}
</ReactErrorBoundary>
);
}
// Default: render null on error
return (
<ReactErrorBoundary
fallbackRender={defaultFallback}
onError={onError}
onReset={onReset}
>
{children}
</ReactErrorBoundary>
);
}
export { type FallbackProps, useErrorBoundary, withErrorBoundary };

View File

@@ -29,7 +29,7 @@ import {
ErrorBoundary,
ErrorBoundaryProps,
FallbackProps,
} from 'react-error-boundary';
} from './ErrorBoundary';
import { ParentSize } from '@visx/responsive';
import { createSelector } from 'reselect';
import { withTheme } from '@emotion/react';

View File

@@ -26,6 +26,13 @@ export { ChartProps };
export type { ChartPropsConfig };
export { default as createLoadableRenderer } from './components/createLoadableRenderer';
export {
ErrorBoundary,
type ErrorBoundaryProps,
type FallbackProps,
useErrorBoundary,
withErrorBoundary,
} from './components/ErrorBoundary';
export { default as reactify } from './components/reactify';
export { default as SuperChart } from './components/SuperChart';

View File

@@ -21,7 +21,7 @@ import '@testing-library/jest-dom';
import { render, screen } from '@superset-ui/core/spec';
import mockConsole, { RestoreConsole } from 'jest-mock-console';
import { triggerResizeObserver } from 'resize-observer-polyfill';
import { ErrorBoundary } from 'react-error-boundary';
import { ErrorBoundary } from '../../../src/chart/components/ErrorBoundary';
import { promiseTimeout, SuperChart } from '@superset-ui/core';
import { WrapperProps } from '../../../src/chart/components/SuperChart';

View File

@@ -33,7 +33,7 @@ import { styled } from '@apache-superset/core/ui';
import type { ChartState, Datasource, ChartStatus } from 'src/explore/types';
import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants';
import { EmptyState, Loading } from '@superset-ui/core/components';
import { ErrorBoundary } from 'src/components';
import { ErrorBoundary } from '@superset-ui/core';
import { Logger, LOG_ACTIONS_RENDER_CHART } from 'src/logger/LogUtils';
import { URL_PARAMS } from 'src/constants';
import { getUrlParam } from 'src/utils/urlUtils';
@@ -396,10 +396,7 @@ class Chart extends PureComponent<ChartProps, {}> {
}
return (
<ErrorBoundary
onError={this.handleRenderContainerFailure}
showMessage={false}
>
<ErrorBoundary onError={this.handleRenderContainerFailure}>
<Styles
data-ui-anchor="chart"
className="chart-container"

View File

@@ -1,63 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ReactElement } from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import type { ErrorBoundaryProps } from './types';
import { ErrorBoundary } from '.';
const mockedProps: Partial<ErrorBoundaryProps> = {
children: <span>Error children</span>,
onError: jest.fn(),
showMessage: false,
};
const Child = (): ReactElement => {
throw new Error('Thrown error');
};
test('should render', () => {
const { container } = render(
<ErrorBoundary {...mockedProps}>
<Child />
</ErrorBoundary>,
);
expect(container).toBeInTheDocument();
});
test('should not render an error message', () => {
render(
<ErrorBoundary {...mockedProps}>
<Child />
</ErrorBoundary>,
);
expect(screen.queryByText('Unexpected error')).not.toBeInTheDocument();
});
test('should render an error message', () => {
const messageProps = {
...mockedProps,
showMessage: true,
};
render(
<ErrorBoundary {...messageProps}>
<Child />
</ErrorBoundary>,
);
expect(screen.getByText('Unexpected error')).toBeInTheDocument();
});

View File

@@ -1,63 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Component, ErrorInfo } from 'react';
import { t } from '@apache-superset/core';
import { ErrorAlert } from '../ErrorMessage';
import type { ErrorBoundaryProps, ErrorBoundaryState } from './types';
export class ErrorBoundary extends Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
static defaultProps: Partial<ErrorBoundaryProps> = {
showMessage: true,
};
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { error: null, info: null };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
this.props.onError?.(error, info);
this.setState({ error, info });
}
render() {
const { error, info } = this.state;
const { showMessage, className } = this.props;
if (error) {
const firstLine = error.toString().split('\n')[0];
if (showMessage) {
return (
<ErrorAlert
errorType={t('Unexpected error')}
message={firstLine}
descriptionDetails={info?.componentStack}
className={className}
/>
);
}
return null;
}
return this.props.children;
}
}
export type { ErrorBoundaryProps };

View File

@@ -1,31 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { ErrorInfo, ReactNode } from 'react';
export interface ErrorBoundaryProps {
children: ReactNode;
onError?: (error: Error, info: ErrorInfo) => void;
showMessage?: boolean;
className?: string;
}
export interface ErrorBoundaryState {
error: Error | null;
info: ErrorInfo | null;
}

View File

@@ -36,7 +36,6 @@ export { DatabaseSelector, type DatabaseObject } from './DatabaseSelector';
export * from './Datasource';
export * from './ErrorMessage';
export { ImportModal, type ImportModelsModalProps } from './ImportModal';
export { ErrorBoundary, type ErrorBoundaryProps } from './ErrorBoundary';
export * from './GenericLink';
export { GridTable, type TableProps } from './GridTable';
export * from './Tag';

View File

@@ -24,7 +24,8 @@ import { addAlpha, JsonObject, useElementOnScreen } from '@superset-ui/core';
import { css, styled, useTheme } from '@apache-superset/core/ui';
import { useDispatch, useSelector } from 'react-redux';
import { EmptyState, Loading } from '@superset-ui/core/components';
import { ErrorBoundary, BasicErrorAlert } from 'src/components';
import { ErrorBoundary } from '@superset-ui/core';
import { BasicErrorAlert } from 'src/components';
import BuilderComponentPane from 'src/dashboard/components/BuilderComponentPane';
import DashboardHeader from 'src/dashboard/components/Header';
import { Icons } from '@superset-ui/core/components/Icons';

View File

@@ -18,7 +18,7 @@
*/
import { ReactNode, useState, useCallback } from 'react';
import type { FormInstance } from '@superset-ui/core/components';
import { ErrorBoundary } from 'src/components/ErrorBoundary';
import { ErrorBoundary } from '@superset-ui/core';
import { BaseModalBody, BaseForm, BaseModalWrapper } from './SharedStyles';
import { ModalFooter } from './ModalFooter';

View File

@@ -22,7 +22,7 @@ import { t } from '@apache-superset/core';
import { ChartCustomizationType, NativeFilterType } from '@superset-ui/core';
import { styled, css, useTheme } from '@apache-superset/core/ui';
import { Constants, Form, Icons, Flex } from '@superset-ui/core/components';
import { ErrorBoundary } from 'src/components';
import { ErrorBoundary } from '@superset-ui/core';
import { testWithId } from 'src/utils/testUtils';
import useEffectEvent from 'src/hooks/useEffectEvent';
import {

View File

@@ -32,7 +32,7 @@ import setupPlugins from 'src/setup/setupPlugins';
import { useUiConfig } from 'src/components/UiConfigContext';
import { store, USER_LOADED } from 'src/views/store';
import { Loading } from '@superset-ui/core/components';
import { ErrorBoundary } from 'src/components';
import { ErrorBoundary } from '@superset-ui/core';
import { addDangerToast } from 'src/components/MessageToasts/actions';
import ToastContainer from 'src/components/MessageToasts/ToastContainer';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';

View File

@@ -24,7 +24,7 @@ import {
} from '@superset-ui/chart-controls';
import { JsonValue, QueryFormData, usePrevious } from '@superset-ui/core';
import { styled } from '@apache-superset/core/ui';
import { ErrorBoundary } from 'src/components';
import { ErrorBoundary } from '@superset-ui/core';
import { ExploreActions } from 'src/explore/actions/exploreActions';
import controlMap from './controls';

View File

@@ -20,7 +20,7 @@ import type React from 'react';
import { createRef, Component, type RefObject } from 'react';
import type { SupersetTheme } from '@apache-superset/core/ui';
import { Button, Icons, Select } from '@superset-ui/core/components';
import { ErrorBoundary } from 'src/components';
import { ErrorBoundary } from '@superset-ui/core';
import { SupersetClient } from '@superset-ui/core';
import { t } from '@apache-superset/core';
import { styled } from '@apache-superset/core/ui';

View File

@@ -24,7 +24,7 @@ import {
useEffect,
useMemo,
} from 'react';
import { ErrorBoundary } from 'src/components';
import { ErrorBoundary } from '@superset-ui/core';
import { setExtensionsContextValue } from './ExtensionsContextUtils';
import ExtensionPlaceholder from './ExtensionPlaceholder';

View File

@@ -43,7 +43,7 @@ import ChartCard from 'src/features/charts/ChartCard';
import Chart from 'src/types/Chart';
import handleResourceExport from 'src/utils/export';
import { Loading } from '@superset-ui/core/components';
import { ErrorBoundary } from 'src/components';
import { ErrorBoundary } from '@superset-ui/core';
import { Icons } from '@superset-ui/core/components/Icons';
import { navigateTo } from 'src/utils/navigationUtils';
import EmptyState from './EmptyState';

View File

@@ -27,7 +27,7 @@ import { bindActionCreators } from 'redux';
import { css } from '@apache-superset/core/ui';
import { Layout, Loading } from '@superset-ui/core/components';
import { setupAGGridModules } from '@superset-ui/core/components/ThemedAgGridReact';
import { ErrorBoundary } from 'src/components';
import { ErrorBoundary } from '@superset-ui/core';
import Menu from 'src/features/home/Menu';
import getBootstrapData, { applicationRoot } from 'src/utils/getBootstrapData';
import ToastContainer from 'src/components/MessageToasts/ToastContainer';
@@ -91,11 +91,7 @@ const App = () => (
flex-direction: column;
`}
>
<ErrorBoundary
css={css`
margin: 16px;
`}
>
<ErrorBoundary>
<Component user={bootstrapData.user} {...props} />
</ErrorBoundary>
</Layout.Content>