mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: react hooks eslint fixes in grafana-ui (#28026)
* Fix some rule violation in grafan-ui * Update eslint-plugin-react-hooks to latest * Remove duplicate dependency * Fix more files * Props destruction
This commit is contained in:
@@ -34,8 +34,9 @@ export default {
|
||||
},
|
||||
};
|
||||
|
||||
export const basic = () => {
|
||||
export const Basic = () => {
|
||||
const { value, title, colorMode, graphMode, height, width, color, textMode, justifyMode } = getKnobs();
|
||||
const theme = useTheme();
|
||||
const sparkline = {
|
||||
xMin: 0,
|
||||
xMax: 5,
|
||||
@@ -51,8 +52,7 @@ export const basic = () => {
|
||||
|
||||
return (
|
||||
<BigValue
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
theme={useTheme()}
|
||||
theme={theme}
|
||||
width={width}
|
||||
height={height}
|
||||
colorMode={colorMode}
|
||||
|
||||
@@ -28,8 +28,8 @@ const getTooltipContainerStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
});
|
||||
|
||||
export const TooltipContainer: React.FC<TooltipContainerProps> = ({
|
||||
position,
|
||||
offset,
|
||||
position: { x: positionX, y: positionY },
|
||||
offset: { x: offsetX, y: offsetY },
|
||||
children,
|
||||
className,
|
||||
...otherProps
|
||||
@@ -38,8 +38,8 @@ export const TooltipContainer: React.FC<TooltipContainerProps> = ({
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
const { width, height } = useWindowSize();
|
||||
const [placement, setPlacement] = useState({
|
||||
x: position.x + offset.x,
|
||||
y: position.y + offset.y,
|
||||
x: positionX + offsetX,
|
||||
y: positionY + offsetY,
|
||||
});
|
||||
|
||||
// Make sure tooltip does not overflow window
|
||||
@@ -48,8 +48,8 @@ export const TooltipContainer: React.FC<TooltipContainerProps> = ({
|
||||
yO = 0;
|
||||
if (tooltipRef && tooltipRef.current) {
|
||||
const measurement = tooltipRef.current.getBoundingClientRect();
|
||||
const xOverflow = width - (position.x + measurement.width);
|
||||
const yOverflow = height - (position.y + measurement.height);
|
||||
const xOverflow = width - (positionX + measurement.width);
|
||||
const yOverflow = height - (positionY + measurement.height);
|
||||
if (xOverflow < 0) {
|
||||
xO = measurement.width;
|
||||
}
|
||||
@@ -60,10 +60,10 @@ export const TooltipContainer: React.FC<TooltipContainerProps> = ({
|
||||
}
|
||||
|
||||
setPlacement({
|
||||
x: position.x + offset.x - xO,
|
||||
y: position.y + offset.y - yO,
|
||||
x: positionX + offsetX - xO,
|
||||
y: positionY + offsetY - yO,
|
||||
});
|
||||
}, [tooltipRef, position]);
|
||||
}, [tooltipRef, width, height, positionX, offsetX, positionY, offsetY]);
|
||||
|
||||
const styles = getTooltipContainerStyles(theme);
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ export const ContextMenu: React.FC<ContextMenuProps> = React.memo(({ x, y, onClo
|
||||
top: collisions.bottom ? y - rect.height - OFFSET : y + OFFSET,
|
||||
});
|
||||
}
|
||||
}, [menuRef.current]);
|
||||
}, [x, y]);
|
||||
|
||||
useClickAway(menuRef, () => {
|
||||
if (onClose) {
|
||||
|
||||
@@ -82,7 +82,7 @@ export const DataLinkInput: React.FC<DataLinkInputProps> = memo(
|
||||
stateRef.current = { showingSuggestions, suggestions, suggestionsIndex, linkUrl, onChange };
|
||||
|
||||
// SelectionReference is used to position the variables suggestion relatively to current DOM selection
|
||||
const selectionRef = useMemo(() => new SelectionReference(), [setShowingSuggestions, linkUrl]);
|
||||
const selectionRef = useMemo(() => new SelectionReference(), []);
|
||||
|
||||
const onKeyDown = React.useCallback((event: KeyboardEvent, next: () => any) => {
|
||||
if (!stateRef.current.showingSuggestions) {
|
||||
|
||||
@@ -68,7 +68,7 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = props => {
|
||||
...change,
|
||||
});
|
||||
},
|
||||
[dataSourceConfig]
|
||||
[dataSourceConfig, onChange]
|
||||
);
|
||||
|
||||
switch (dataSourceConfig.access) {
|
||||
|
||||
@@ -40,13 +40,16 @@ export const FileUpload: FC<Props> = ({
|
||||
const style = getStyles(theme, size);
|
||||
const [fileName, setFileName] = useState('');
|
||||
|
||||
const onChange = useCallback((event: FormEvent<HTMLInputElement>) => {
|
||||
const file = event.currentTarget?.files?.[0];
|
||||
if (file) {
|
||||
setFileName(file.name ?? '');
|
||||
}
|
||||
onFileUpload(event);
|
||||
}, []);
|
||||
const onChange = useCallback(
|
||||
(event: FormEvent<HTMLInputElement>) => {
|
||||
const file = event.currentTarget?.files?.[0];
|
||||
if (file) {
|
||||
setFileName(file.name ?? '');
|
||||
}
|
||||
onFileUpload(event);
|
||||
},
|
||||
[onFileUpload]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -33,7 +33,7 @@ export function Form<T>({
|
||||
if (validateOnMount) {
|
||||
triggerValidation(validateFieldsOnMount);
|
||||
}
|
||||
}, []);
|
||||
}, [triggerValidation, validateFieldsOnMount, validateOnMount]);
|
||||
|
||||
return (
|
||||
<form
|
||||
|
||||
@@ -56,7 +56,7 @@ export function RadioButtonGroup<T>({
|
||||
fullWidth = false,
|
||||
}: RadioButtonGroupProps<T>) {
|
||||
const handleOnChange = useCallback(
|
||||
(option: SelectableValue<T>) => {
|
||||
(option: SelectableValue) => {
|
||||
return () => {
|
||||
if (onChange) {
|
||||
onChange(option.value);
|
||||
|
||||
@@ -147,7 +147,7 @@ const LogRowContextGroup: React.FunctionComponent<LogRowContextGroupProps> = ({
|
||||
if (shouldScrollToBottom && listContainerRef.current) {
|
||||
setScrollTop(listContainerRef.current.offsetHeight);
|
||||
}
|
||||
});
|
||||
}, [shouldScrollToBottom]);
|
||||
|
||||
const headerProps = {
|
||||
row,
|
||||
@@ -197,18 +197,17 @@ export const LogRowContext: React.FunctionComponent<LogRowContextProps> = ({
|
||||
onLoadMoreContext,
|
||||
hasMoreContextRows,
|
||||
}) => {
|
||||
const handleEscKeyDown = (e: KeyboardEvent): void => {
|
||||
if (e.keyCode === 27) {
|
||||
onOutsideClick();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscKeyDown = (e: KeyboardEvent): void => {
|
||||
if (e.keyCode === 27) {
|
||||
onOutsideClick();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEscKeyDown, false);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleEscKeyDown, false);
|
||||
};
|
||||
}, []);
|
||||
}, [onOutsideClick]);
|
||||
|
||||
return (
|
||||
<ClickOutsideWrapper onClick={onOutsideClick}>
|
||||
|
||||
@@ -4,13 +4,13 @@ import { FieldMatcherID, fieldMatchers } from '@grafana/data';
|
||||
import { Input } from '../Input/Input';
|
||||
|
||||
export const FieldNameByRegexMatcherEditor = memo<MatcherUIProps<string>>(props => {
|
||||
const { options } = props;
|
||||
const { options, onChange } = props;
|
||||
|
||||
const onBlur = useCallback(
|
||||
(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
return props.onChange(e.target.value);
|
||||
return onChange(e.target.value);
|
||||
},
|
||||
[props.onChange]
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return <Input placeholder="Enter regular expression" defaultValue={options} onBlur={onBlur} />;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FieldMatcherID, fieldMatchers, getFieldDisplayName, SelectableValue, Da
|
||||
import { Select } from '../Select/Select';
|
||||
|
||||
export const FieldNameMatcherEditor = memo<MatcherUIProps<string>>(props => {
|
||||
const { data, options } = props;
|
||||
const { data, options, onChange: onChangeFromProps } = props;
|
||||
const names = useFieldDisplayNames(data);
|
||||
const selectOptions = useSelectOptions(names);
|
||||
|
||||
@@ -13,9 +13,9 @@ export const FieldNameMatcherEditor = memo<MatcherUIProps<string>>(props => {
|
||||
if (!selection.value || !names.has(selection.value)) {
|
||||
return;
|
||||
}
|
||||
return props.onChange(selection.value);
|
||||
return onChangeFromProps(selection.value);
|
||||
},
|
||||
[names, props.onChange]
|
||||
[names, onChangeFromProps]
|
||||
);
|
||||
|
||||
const selectedOption = selectOptions.find(v => v.value === options);
|
||||
|
||||
@@ -4,15 +4,15 @@ import { FieldMatcherID, fieldMatchers, SelectableValue, FieldType, DataFrame }
|
||||
import { Select } from '../Select/Select';
|
||||
|
||||
export const FieldTypeMatcherEditor = memo<MatcherUIProps<string>>(props => {
|
||||
const { data, options } = props;
|
||||
const { data, options, onChange: onChangeFromProps } = props;
|
||||
const counts = useFieldCounts(data);
|
||||
const selectOptions = useSelectOptions(counts, options);
|
||||
|
||||
const onChange = useCallback(
|
||||
(selection: SelectableValue<string>) => {
|
||||
return props.onChange(selection.value!);
|
||||
return onChangeFromProps(selection.value!);
|
||||
},
|
||||
[counts, props.onChange]
|
||||
[onChangeFromProps]
|
||||
);
|
||||
|
||||
const selectedOption = selectOptions.find(v => v.value === options);
|
||||
|
||||
@@ -36,7 +36,7 @@ export const NumberValueEditor: React.FC<FieldConfigEditorProps<number, NumberFi
|
||||
);
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
[onChange, settings?.integer]
|
||||
);
|
||||
|
||||
const defaultValue = value === undefined || value === null || isNaN(value) ? '' : value.toString();
|
||||
|
||||
@@ -24,7 +24,7 @@ export const StringValueEditor: React.FC<FieldConfigEditorProps<string, StringFi
|
||||
onChange(evt.currentTarget.value.trim() === '' ? undefined : evt.currentTarget.value);
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
[item.settings?.useTextarea, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -144,7 +144,7 @@ export function SelectBase<T>({
|
||||
}
|
||||
onChange(value);
|
||||
},
|
||||
[isMulti, value, onChange]
|
||||
[isMulti, onChange]
|
||||
);
|
||||
let ReactSelectComponent: ReactSelect | Creatable = ReactSelect;
|
||||
const creatableProps: any = {};
|
||||
|
||||
@@ -24,7 +24,7 @@ export const FilterList: FC<Props> = ({ options, values, onChange }) => {
|
||||
searchFilter,
|
||||
]);
|
||||
const gutter = parseInt(theme.spacing.sm, 10);
|
||||
const height = useMemo(() => Math.min(items.length * ITEM_HEIGHT, MIN_HEIGHT) + gutter, [items]);
|
||||
const height = useMemo(() => Math.min(items.length * ITEM_HEIGHT, MIN_HEIGHT) + gutter, [gutter, items.length]);
|
||||
|
||||
const onInputChange = useCallback(
|
||||
(event: React.FormEvent<HTMLInputElement>) => {
|
||||
|
||||
@@ -47,13 +47,12 @@ export interface Props {
|
||||
|
||||
interface ReactTableInternalState extends UseResizeColumnsState<{}>, UseSortByState<{}>, UseFiltersState<{}> {}
|
||||
|
||||
function useTableStateReducer(props: Props) {
|
||||
function useTableStateReducer({ onColumnResize, onSortByChange, data }: Props) {
|
||||
return useCallback(
|
||||
(newState: ReactTableInternalState, action: any) => {
|
||||
switch (action.type) {
|
||||
case 'columnDoneResizing':
|
||||
if (props.onColumnResize) {
|
||||
const { data } = props;
|
||||
if (onColumnResize) {
|
||||
const info = (newState.columnResizing.headerIdWidths as any)[0];
|
||||
const columnIdString = info[0];
|
||||
const fieldIndex = parseInt(columnIdString, 10);
|
||||
@@ -65,11 +64,10 @@ function useTableStateReducer(props: Props) {
|
||||
}
|
||||
|
||||
const fieldDisplayName = getFieldDisplayName(field, data);
|
||||
props.onColumnResize(fieldDisplayName, width);
|
||||
onColumnResize(fieldDisplayName, width);
|
||||
}
|
||||
case 'toggleSortBy':
|
||||
if (props.onSortByChange) {
|
||||
const { data } = props;
|
||||
if (onSortByChange) {
|
||||
const sortByFields: TableSortByFieldState[] = [];
|
||||
|
||||
for (const sortItem of newState.sortBy) {
|
||||
@@ -84,24 +82,24 @@ function useTableStateReducer(props: Props) {
|
||||
});
|
||||
}
|
||||
|
||||
props.onSortByChange(sortByFields);
|
||||
onSortByChange(sortByFields);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return newState;
|
||||
},
|
||||
[props.onColumnResize, props.onSortByChange, props.data]
|
||||
[data, onColumnResize, onSortByChange]
|
||||
);
|
||||
}
|
||||
|
||||
function getInitialState(props: Props, columns: Column[]): Partial<ReactTableInternalState> {
|
||||
function getInitialState(initialSortBy: Props['initialSortBy'], columns: Column[]): Partial<ReactTableInternalState> {
|
||||
const state: Partial<ReactTableInternalState> = {};
|
||||
|
||||
if (props.initialSortBy) {
|
||||
if (initialSortBy) {
|
||||
state.sortBy = [];
|
||||
|
||||
for (const sortBy of props.initialSortBy) {
|
||||
for (const sortBy of initialSortBy) {
|
||||
for (const col of columns) {
|
||||
if (col.Header === sortBy.displayName) {
|
||||
state.sortBy.push({ id: col.id as string, desc: sortBy.desc });
|
||||
@@ -123,6 +121,7 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
columnMinWidth = COLUMN_MIN_WIDTH,
|
||||
noHeader,
|
||||
resizable = true,
|
||||
initialSortBy,
|
||||
} = props;
|
||||
const theme = useTheme();
|
||||
const tableStyles = getTableStyles(theme);
|
||||
@@ -151,9 +150,9 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
data: memoizedData,
|
||||
disableResizing: !resizable,
|
||||
stateReducer: stateReducer,
|
||||
initialState: getInitialState(props, memoizedColumns),
|
||||
initialState: getInitialState(initialSortBy, memoizedColumns),
|
||||
}),
|
||||
[memoizedColumns, memoizedData, stateReducer, resizable]
|
||||
[initialSortBy, memoizedColumns, memoizedData, resizable, stateReducer]
|
||||
);
|
||||
|
||||
const { getTableProps, headerGroups, rows, prepareRow, totalColumnsWidth } = useTable(
|
||||
@@ -164,6 +163,8 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
useResizeColumns
|
||||
);
|
||||
|
||||
const { fields } = data;
|
||||
|
||||
const RenderRow = React.useCallback(
|
||||
({ index, style }) => {
|
||||
const row = rows[index];
|
||||
@@ -173,7 +174,7 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
{row.cells.map((cell: Cell, index: number) => (
|
||||
<TableCell
|
||||
key={index}
|
||||
field={data.fields[index]}
|
||||
field={fields[index]}
|
||||
tableStyles={tableStyles}
|
||||
cell={cell}
|
||||
onCellFilterAdded={onCellFilterAdded}
|
||||
@@ -182,7 +183,7 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[prepareRow, rows]
|
||||
[fields, onCellFilterAdded, prepareRow, rows, tableStyles]
|
||||
);
|
||||
|
||||
const headerHeight = noHeader ? 0 : tableStyles.cellHeight;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FormEvent, memo, useCallback, useEffect, useState } from 'react';
|
||||
import React, { FormEvent, memo, useCallback } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import Calendar from 'react-calendar/dist/entry.nostyle';
|
||||
import { dateTime, DateTime, dateTimeParse, GrafanaTheme, TimeZone } from '@grafana/data';
|
||||
@@ -244,15 +244,11 @@ const Header = memo<Props>(({ onClose }) => {
|
||||
});
|
||||
|
||||
const Body = memo<Props>(({ onChange, from, to, timeZone }) => {
|
||||
const [value, setValue] = useState<Date[]>();
|
||||
const value = inputToValue(from, to);
|
||||
const theme = useTheme();
|
||||
const onCalendarChange = useOnCalendarChange(onChange, timeZone);
|
||||
const styles = getBodyStyles(theme);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(inputToValue(from, to));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
selectRange={true}
|
||||
@@ -309,7 +305,7 @@ function useOnCalendarChange(onChange: (from: DateTime, to: DateTime) => void, t
|
||||
|
||||
onChange(from, to);
|
||||
},
|
||||
[onChange]
|
||||
[onChange, timeZone]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,15 +192,15 @@ export const TimePickerContent: React.FC<Props> = props => {
|
||||
};
|
||||
|
||||
const NarrowScreenForm: React.FC<FormProps> = props => {
|
||||
if (!props.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const theme = useTheme();
|
||||
const styles = getNarrowScreenStyles(theme);
|
||||
const isAbsolute = isDateTime(props.value.raw.from) || isDateTime(props.value.raw.to);
|
||||
const [collapsed, setCollapsed] = useState(isAbsolute);
|
||||
|
||||
if (!props.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -238,13 +238,13 @@ const NarrowScreenForm: React.FC<FormProps> = props => {
|
||||
};
|
||||
|
||||
const FullScreenForm: React.FC<FormProps> = props => {
|
||||
const theme = useTheme();
|
||||
const styles = getFullScreenStyles(theme);
|
||||
|
||||
if (!props.visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const theme = useTheme();
|
||||
const styles = getFullScreenStyles(theme);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.container}>
|
||||
|
||||
@@ -32,7 +32,7 @@ interface InputState {
|
||||
const errorMessage = 'Please enter a past date or "now"';
|
||||
|
||||
export const TimeRangeForm: React.FC<Props> = props => {
|
||||
const { value, isFullscreen = false, timeZone, roundup } = props;
|
||||
const { value, isFullscreen = false, timeZone, onApply: onApplyFromProps } = props;
|
||||
|
||||
const [from, setFrom] = useState<InputState>(valueToState(value.raw.from, false, timeZone));
|
||||
const [to, setTo] = useState<InputState>(valueToState(value.raw.to, true, timeZone));
|
||||
@@ -72,9 +72,9 @@ export const TimeRangeForm: React.FC<Props> = props => {
|
||||
const raw: RawTimeRange = { from: from.value, to: to.value };
|
||||
const timeRange = rangeUtil.convertRawToRange(raw, timeZone);
|
||||
|
||||
props.onApply(timeRange);
|
||||
onApplyFromProps(timeRange);
|
||||
},
|
||||
[from, to, roundup, timeZone]
|
||||
[from.invalid, from.value, onApplyFromProps, timeZone, to.invalid, to.value]
|
||||
);
|
||||
|
||||
const onChange = useCallback(
|
||||
|
||||
@@ -63,7 +63,7 @@ interface SelectableZoneGroup extends SelectableValue<string> {
|
||||
const useTimeZones = (includeInternal: boolean): SelectableZoneGroup[] => {
|
||||
const now = Date.now();
|
||||
|
||||
return getTimeZoneGroups(includeInternal).map((group: GroupedTimeZones) => {
|
||||
const timeZoneGroups = getTimeZoneGroups(includeInternal).map((group: GroupedTimeZones) => {
|
||||
const options = group.zones.reduce((options: SelectableZone[], zone) => {
|
||||
const info = getTimeZoneInfo(zone, now);
|
||||
|
||||
@@ -74,7 +74,7 @@ const useTimeZones = (includeInternal: boolean): SelectableZoneGroup[] => {
|
||||
options.push({
|
||||
label: info.name,
|
||||
value: info.zone,
|
||||
searchIndex: useSearchIndex(info, now),
|
||||
searchIndex: getSearchIndex(info, now),
|
||||
});
|
||||
|
||||
return options;
|
||||
@@ -85,6 +85,7 @@ const useTimeZones = (includeInternal: boolean): SelectableZoneGroup[] => {
|
||||
options,
|
||||
};
|
||||
});
|
||||
return timeZoneGroups;
|
||||
};
|
||||
|
||||
const useSelectedTimeZone = (
|
||||
@@ -135,19 +136,17 @@ const useFilterBySearchIndex = () => {
|
||||
}, []);
|
||||
};
|
||||
|
||||
const useSearchIndex = (info: TimeZoneInfo, timestamp: number): string => {
|
||||
return useMemo(() => {
|
||||
const parts: string[] = [
|
||||
toLower(info.name),
|
||||
toLower(info.abbreviation),
|
||||
toLower(formatUtcOffset(timestamp, info.zone)),
|
||||
];
|
||||
const getSearchIndex = (info: TimeZoneInfo, timestamp: number): string => {
|
||||
const parts: string[] = [
|
||||
toLower(info.name),
|
||||
toLower(info.abbreviation),
|
||||
toLower(formatUtcOffset(timestamp, info.zone)),
|
||||
];
|
||||
|
||||
for (const country of info.countries) {
|
||||
parts.push(toLower(country.name));
|
||||
parts.push(toLower(country.code));
|
||||
}
|
||||
for (const country of info.countries) {
|
||||
parts.push(toLower(country.name));
|
||||
parts.push(toLower(country.code));
|
||||
}
|
||||
|
||||
return parts.join('|');
|
||||
}, [info.zone, info.abbreviation, info.offsetInMins]);
|
||||
return parts.join('|');
|
||||
};
|
||||
|
||||
@@ -8,21 +8,25 @@ interface Props {
|
||||
}
|
||||
|
||||
export const TimeZoneDescription: React.FC<PropsWithChildren<Props>> = ({ info }) => {
|
||||
if (!info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme);
|
||||
const description = useDescription(info);
|
||||
|
||||
if (!info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className={styles.description}>{description}</div>;
|
||||
};
|
||||
|
||||
const useDescription = (info: TimeZoneInfo): string => {
|
||||
const useDescription = (info?: TimeZoneInfo): string => {
|
||||
return useMemo(() => {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (!info) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (info.countries.length > 0) {
|
||||
const country = info.countries[0];
|
||||
parts.push(country.name);
|
||||
@@ -33,7 +37,7 @@ const useDescription = (info: TimeZoneInfo): string => {
|
||||
}
|
||||
|
||||
return parts.join(', ');
|
||||
}, [info.zone]);
|
||||
}, [info]);
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
|
||||
Reference in New Issue
Block a user