diff --git a/src/components/ChartKit.tsx b/src/components/ChartKit.tsx index 1dcdbcc7..b457ac03 100644 --- a/src/components/ChartKit.tsx +++ b/src/components/ChartKit.tsx @@ -1,7 +1,8 @@ import React from 'react'; import block from 'bem-cn-lite'; -import {settings} from '../libs'; -import {getRandomCKId} from '../utils'; +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 {Loader} from './Loader/Loader'; @@ -12,49 +13,67 @@ import './ChartKit.scss'; const b = block('chartkit'); -const ChartKitComponent = React.forwardRef>( - (props, ref) => { - const widgetRef = React.useRef(); - const {id = getRandomCKId(), type, data, onLoad, onError, ...restProps} = props; - const lang = settings.get('lang'); - const plugins = settings.get('plugins'); - const plugin = plugins.find((iteratedPlugin) => iteratedPlugin.type === type); - - if (!plugin) { - return null; - } - - const ChartComponent = plugin.renderer; - - React.useImperativeHandle( - ref, - () => ({ - reflow(details) { - if (widgetRef.current?.reflow) { - widgetRef.current.reflow(details); - } - }, - }), - [], - ); - - return ( - - }> -
- -
-
-
- ); - }, -); - -export const ChartKit = React.memo(ChartKitComponent); +type ChartKitComponentProps = Omit, 'onError'> & { + instanceRef?: React.ForwardedRef; +}; + +const ChartKitComponent = (props: ChartKitComponentProps) => { + const widgetRef = React.useRef(); + const {instanceRef, id = getRandomCKId(), type, data, onLoad, ...restProps} = props; + const lang = settings.get('lang'); + const plugins = settings.get('plugins'); + const plugin = plugins.find((iteratedPlugin) => iteratedPlugin.type === type); + + if (!plugin) { + throw new ChartKitError({ + code: CHARTKIT_ERROR_CODE.UNKNOWN_PLUGIN, + message: i18n('error', 'label_unknown-plugin', {type}), + }); + } + + const ChartComponent = plugin.renderer; + + React.useImperativeHandle( + instanceRef, + () => ({ + reflow(details) { + if (widgetRef.current?.reflow) { + widgetRef.current.reflow(details); + } + }, + }), + [], + ); + + return ( + }> +
+ +
+
+ ); +}; + +const ChartKitComponentWithErrorBoundary = React.forwardRef< + ChartKitRef | undefined, + ChartKitProps +>((props, ref) => { + const {onError, ...componentProps} = props; + + return ( + + + + ); +}) /* https://stackoverflow.com/a/58473012 */ as ( + props: ChartKitProps & {ref?: React.ForwardedRef}, +) => ReturnType; + +export const ChartKit = typedMemo(ChartKitComponentWithErrorBoundary); diff --git a/src/components/ErrorBoundary/ErrorBoundary.tsx b/src/components/ErrorBoundary/ErrorBoundary.tsx index 1ed128ac..2b91bd54 100644 --- a/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type {ChartKitError} from '../../libs'; import type {ChartKitOnError} from '../../types'; import {ErrorView} from '../ErrorView/ErrorView'; @@ -7,7 +8,7 @@ type Props = { }; type State = { - error?: Error; + error?: ChartKitError | Error; }; export class ErrorBoundary extends React.Component { @@ -28,8 +29,10 @@ export class ErrorBoundary extends React.Component { } render() { - if (this.state.error) { - return ; + const {error} = this.state; + + if (error) { + return ; } return this.props.children; diff --git a/src/components/ErrorView/ErrorView.scss b/src/components/ErrorView/ErrorView.scss deleted file mode 100644 index d4496b8a..00000000 --- a/src/components/ErrorView/ErrorView.scss +++ /dev/null @@ -1,23 +0,0 @@ -.chartkit-error { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - padding: 16px; - overflow: auto; - - &__title { - display: flex; - align-items: center; - color: var(--error-title-text); - font-size: 13px; - line-height: 16px; - margin-bottom: 12px; - } - - &__message { - padding-left: 28px; - margin-bottom: 8px; - color: var(--error-message-text); - } -} diff --git a/src/components/ErrorView/ErrorView.tsx b/src/components/ErrorView/ErrorView.tsx index 2e71f68f..02743852 100644 --- a/src/components/ErrorView/ErrorView.tsx +++ b/src/components/ErrorView/ErrorView.tsx @@ -1,16 +1,13 @@ import React from 'react'; -import block from 'bem-cn-lite'; import {i18n} from '../../i18n'; +import type {ChartKitError} from '../../libs'; -import './ErrorView.scss'; +type Props = { + error: ChartKitError | Error; +}; -const b = block('chartkit-error'); +export const ErrorView = ({error}: Props) => { + const message = error.message || i18n('error', 'label_unknown-error'); -export const ErrorView = () => { - return ( -
-
{i18n('common', 'error')}
-
{i18n('common', 'error-unknown-extension')}
-
- ); + return
{message}
; }; diff --git a/src/i18n/keysets/en.json b/src/i18n/keysets/en.json index c6c14c9d..1675a4c4 100644 --- a/src/i18n/keysets/en.json +++ b/src/i18n/keysets/en.json @@ -1,8 +1,10 @@ { "common": { - "error": "Error", - "error-unknown-extension": "Unknown chart type", "tooltip-sum": "Sum", "tooltip-rest": "Rest" + }, + "error": { + "label_unknown-plugin": "Unknown plugin type \"{{type}}\"", + "label_unknown-error": "Unknown error" } } diff --git a/src/i18n/keysets/ru.json b/src/i18n/keysets/ru.json index 45c20c3f..a3f0f8cd 100644 --- a/src/i18n/keysets/ru.json +++ b/src/i18n/keysets/ru.json @@ -1,8 +1,10 @@ { "common": { - "error": "Ошибка", - "error-unknown-extension": "Неизвестный тип чарта", "tooltip-sum": "Сумма", "tooltip-rest": "Остальные" + }, + "error": { + "label_unknown-plugin": "Неизвестный тип плагина \"{{type}}\"", + "label_unknown-error": "Неизвестная ошибка" } } diff --git a/src/libs/chartkit-error/chartkit-error.ts b/src/libs/chartkit-error/chartkit-error.ts index 30008a3c..0970d40e 100644 --- a/src/libs/chartkit-error/chartkit-error.ts +++ b/src/libs/chartkit-error/chartkit-error.ts @@ -5,9 +5,9 @@ export type ChartKitErrorArgs = { }; export const CHARTKIT_ERROR_CODE = { + NO_DATA: 'ERR.CK.NO_DATA', UNKNOWN: 'ERR.CK.UNKNOWN_ERROR', UNKNOWN_PLUGIN: 'ERR.CK.UNKNOWN_PLUGIN', - NO_DATA: 'ERR.CK.NO_DATA', }; export class ChartKitError extends Error { diff --git a/src/utils/index.ts b/src/utils/index.ts index 23a2408e..632e4f9e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1 +1,2 @@ export {getRandomCKId} from './common'; +export {typedMemo} from './react'; diff --git a/src/utils/react.ts b/src/utils/react.ts new file mode 100644 index 00000000..e57c7f2f --- /dev/null +++ b/src/utils/react.ts @@ -0,0 +1,5 @@ +import React from 'react'; + +// For some reason React.memo drops the generic prop type and creates a regular union type +// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/37087#issuecomment-542793243 +export const typedMemo: (component: T) => T = React.memo;