Skip to content

Commit

Permalink
feat(main): Add renderErrorView prop for custom render and handle errors
Browse files Browse the repository at this point in the history
  • Loading branch information
EgorKluch committed Jun 20, 2023
1 parent ecb6e92 commit b836554
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 78 deletions.
21 changes: 6 additions & 15 deletions src/components/ChartKit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {i18n} from '../i18n';
import {CHARTKIT_ERROR_CODE, ChartKitError, settings} from '../libs';
import {getRandomCKId, typedMemo} from '../utils';
import type {ChartKitType, ChartKitRef, ChartKitWidgetRef, ChartKitProps} from '../types';
import {ErrorBoundary} from './ErrorBoundary/ErrorBoundary';
import {ErrorBoundary} from './ErrorBoundary';
import {Loader} from './Loader/Loader';

import './ChartKit.scss';
Expand Down Expand Up @@ -60,21 +60,12 @@ const ChartKitComponentWithErrorBoundary = React.forwardRef<
ChartKitRef | undefined,
ChartKitProps<ChartKitType>
>(function ChartKitComponentWithErrorBoundary(props, ref) {
const resetErrorRef = React.useRef<(() => void) | null>(null);
const {data} = props;

React.useEffect(() => {
if (resetErrorRef.current) {
resetErrorRef.current();
}
}, [data]);

const handleResetError = React.useCallback((resetError) => {
resetErrorRef.current = resetError;
}, []);

return (
<ErrorBoundary onError={props.onError} resetError={handleResetError}>
<ErrorBoundary
onError={props.onError}
data={props.data}
renderError={props.renderErrorView}
>
<ChartKitComponent instanceRef={ref} {...props} />
</ErrorBoundary>
);
Expand Down
68 changes: 68 additions & 0 deletions src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import type {ChartKitError} from '../libs';
import type {ChartKitOnError, ChartKitType, ChartKitWidget, RenderError} from '../types';
import {getErrorMessage} from '../utils/getErrorMessage';
import {CHARTKIT_ERROR_CODE} from '../libs';

type Props = {
onError?: ChartKitOnError;
data: ChartKitWidget[ChartKitType]['data'];
renderError?: RenderError;
};

type State = {
error?: ChartKitError | Error;
};

export class ErrorBoundary extends React.Component<Props, State> {
static getDerivedStateFromError(error: Error) {
return {error};
}

state: State = {
error: undefined,
};

componentDidCatch() {
const {error} = this.state;

if (error) {
this.props.onError?.({error});
}
}

componentDidUpdate(prevProps: Readonly<Props>) {
if (prevProps.data !== this.props.data) {
const {error} = this.state;
if (error && 'code' in error && error.code === CHARTKIT_ERROR_CODE.NO_DATA) {
this.resetError();
}
}
}

render() {
const {error} = this.state;

if (error) {
const message = getErrorMessage(error);

if (this.props.renderError) {
return this.props.renderError({
error,
message,
resetError: this.resetError,
});
}

return <div>{message}</div>;
}

return this.props.children;
}

resetError = () => {
if (this.state.error) {
this.setState({error: undefined});
}
};
}
45 changes: 0 additions & 45 deletions src/components/ErrorBoundary/ErrorBoundary.tsx

This file was deleted.

14 changes: 0 additions & 14 deletions src/components/ErrorView/ErrorView.tsx

This file was deleted.

15 changes: 12 additions & 3 deletions src/plugins/highcharts/__stories__/components/ChartStory.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import {Button} from '@gravity-ui/uikit';
import {ChartKitRef} from '../../../../types';
import {ChartKitRef, RenderError} from '../../../../types';
import {settings} from '../../../../libs';
import {HighchartsPlugin} from '../../index';
import holidays from '../../mocks/holidays';
Expand All @@ -13,9 +13,11 @@ const DEFAULT_STORY_WIDTH = '100%';
export type ChartStoryProps = {
data: HighchartsWidgetData;

withoutPlugin?: boolean;
visible?: boolean;
height?: string;
width?: string;
renderErrorView?: RenderError;
};
export const ChartStory: React.FC<ChartStoryProps> = (props: ChartStoryProps) => {
const {height, width, data} = props;
Expand All @@ -25,7 +27,9 @@ export const ChartStory: React.FC<ChartStoryProps> = (props: ChartStoryProps) =>
const chartKitRef = React.useRef<ChartKitRef>();

if (!initRef.current) {
settings.set({plugins: [HighchartsPlugin], extra: {holidays}});
if (!props.withoutPlugin) {
settings.set({plugins: [HighchartsPlugin], extra: {holidays}});
}
initRef.current = true;
}

Expand All @@ -40,7 +44,12 @@ export const ChartStory: React.FC<ChartStoryProps> = (props: ChartStoryProps) =>
width: width || DEFAULT_STORY_WIDTH,
}}
>
<ChartKit ref={chartKitRef} type="highcharts" data={data} />
<ChartKit
ref={chartKitRef}
type="highcharts"
data={data}
renderErrorView={props.renderErrorView}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import {Meta, Story} from '@storybook/react';
import {ChartKit} from '../../../../components/ChartKit';
import {ChartStory} from '../components/ChartStory';
import {Button} from '@gravity-ui/uikit';
import {CHARTKIT_ERROR_CODE, settings} from '../../../../libs';
import {HighchartsPlugin} from '../../index';
import holidays from '../../mocks/holidays';
import {noData, filledData} from '../../mocks/custom-error-render';
import {RenderError} from '../../../../types';

export default {
title: 'Plugins/Highcharts/CustomErrorRender',
component: ChartKit,
} as Meta;

const Template: Story = () => {
const [data, setData] = React.useState(noData);

const renderErrorView: RenderError = React.useCallback(({error, message, resetError}) => {
function renderFixButton() {
if (!('code' in error)) {
return null;
}

switch (error.code) {
case CHARTKIT_ERROR_CODE.UNKNOWN_PLUGIN:
return (
<Button
onClick={() => {
settings.set({plugins: [HighchartsPlugin], extra: {holidays}});
resetError();
}}
>
Add highcharts plugin
</Button>
);
case CHARTKIT_ERROR_CODE.NO_DATA:
return (
<Button
onClick={() => {
setData(filledData);
}}
>
Add data
</Button>
);
default:
return null;
}
}

return (
<div>
<h2>{message}</h2>
{renderFixButton()}
</div>
);
}, []);

return (
<div>
<ChartStory
withoutPlugin={true}
data={data}
visible={true}
renderErrorView={renderErrorView}
/>
</div>
);
};

export const CustomErrorRender = Template.bind({});
80 changes: 80 additions & 0 deletions src/plugins/highcharts/mocks/custom-error-render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type {HighchartsWidgetData} from '../types';
import _ from 'lodash';

const baseData: Omit<HighchartsWidgetData, 'data'> = {
config: {
hideHolidays: false,
normalizeDiv: false,
normalizeSub: false,
},
libraryConfig: {
chart: {
type: 'arearange',
},
title: {
text: 'Temperature variation by day',
},
xAxis: {
type: 'datetime',
},
tooltip: {
valueSuffix: '°C',
},
},
};

export const noData: HighchartsWidgetData = {
...baseData,
data: {
graphs: [
{
name: 'Temperatures',
data: [],
},
],
},
};

export const filledData: HighchartsWidgetData = {
...baseData,
data: {
graphs: [
{
name: 'Temperatures',
data: [
[1246406400000, 10.4, 17],
[1246492800000, 10.3, 28.6],
[1246579200000, 14.8, 18.4],
[1246665600000, 11.5, 25.8],
[1246752000000, 11.1, 24.4],
[1246838400000, 17.7, 19.6],
[1246924800000, 15.1, 18.1],
[1247011200000, 15.1, 27.2],
[1247097600000, 17, 17.5],
[1247184000000, 12.6, 18.5],
[1247270400000, 12.2, 26],
[1247356800000, 15.9, 22.9],
[1247443200000, 17.1, 18.1],
[1247529600000, 13.3, 24.2],
[1247616000000, 17, 28.1],
[1247702400000, 16.2, 22.6],
[1247788800000, 10.6, 19],
[1247875200000, 11.3, 19.7],
[1247961600000, 14.1, 24.6],
[1248048000000, 14.2, 22.5],
[1248134400000, 14.1, 28.5],
[1248220800000, 14, 27],
[1248307200000, 10.2, 20.6],
[1248393600000, 13.1, 29.9],
[1248480000000, 13.7, 21.1],
[1248566400000, 15, 28.6],
[1248652800000, 12, 17.5],
[1248739200000, 17.8, 24.4],
[1248825600000, 11.7, 25.9],
[1248912000000, 13.6, 25.6],
[1248998400000, 17.3, 22.2],
],
},
],
},
};
11 changes: 10 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';

import type {ChartKitWidget} from './widget';
import {ChartKitError} from '../libs';

export type {ChartKitHolidays} from './misc';

Expand Down Expand Up @@ -47,7 +48,7 @@ export type ChartKitProps<T extends ChartKitType> = {
* @param data
*/
onChartLoad?: (data: ChartKitOnChartLoad<T>) => void;

renderErrorView?: RenderError;
onError?: ChartKitOnError;
} & {[key in keyof Omit<ChartKitWidget[T], 'data' | 'widget'>]: ChartKitWidget[T][key]};

Expand All @@ -56,4 +57,12 @@ export type ChartKitPlugin = {
renderer: React.LazyExoticComponent<any>;
};

export type RenderErrorOpts = {
message: string;
error: ChartKitError | Error;
resetError: () => void;
};

export type RenderError = (opts: RenderErrorOpts) => React.ReactNode;

export type {ChartKitWidget};
Loading

0 comments on commit b836554

Please sign in to comment.