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:
Zoltán Bedi
2020-10-21 09:06:41 +02:00
committed by GitHub
parent f158e4f526
commit e1c44d7a8a
26 changed files with 108 additions and 107 deletions

View File

@@ -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}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -68,7 +68,7 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = props => {
...change,
});
},
[dataSourceConfig]
[dataSourceConfig, onChange]
);
switch (dataSourceConfig.access) {

View File

@@ -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 (
<>

View File

@@ -33,7 +33,7 @@ export function Form<T>({
if (validateOnMount) {
triggerValidation(validateFieldsOnMount);
}
}, []);
}, [triggerValidation, validateFieldsOnMount, validateOnMount]);
return (
<form

View File

@@ -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);

View File

@@ -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}>

View File

@@ -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} />;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();

View File

@@ -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 (

View File

@@ -144,7 +144,7 @@ export function SelectBase<T>({
}
onChange(value);
},
[isMulti, value, onChange]
[isMulti, onChange]
);
let ReactSelectComponent: ReactSelect | Creatable = ReactSelect;
const creatableProps: any = {};

View File

@@ -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>) => {

View File

@@ -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;

View File

@@ -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]
);
}

View File

@@ -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}>

View File

@@ -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(

View File

@@ -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('|');
};

View File

@@ -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) => {