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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 108 additions and 107 deletions

View File

@ -139,7 +139,7 @@
"eslint-config-prettier": "6.11.0",
"eslint-plugin-jsdoc": "28.6.1",
"eslint-plugin-prettier": "3.1.4",
"eslint-plugin-react-hooks": "4.0.5",
"eslint-plugin-react-hooks": "4.1.2",
"expect.js": "0.3.1",
"expose-loader": "0.7.5",
"file-loader": "5.0.2",
@ -212,17 +212,16 @@
"@types/antlr4": "^4.7.1",
"@types/braintree__sanitize-url": "4.0.0",
"@types/common-tags": "^1.8.0",
"@types/hoist-non-react-statics": "3.3.1",
"@types/jsurl": "^1.2.28",
"@types/md5": "^2.1.33",
"@types/react-loadable": "5.5.2",
"@types/hoist-non-react-statics": "3.3.1",
"@types/react-virtualized-auto-sizer": "1.0.0",
"@types/sockjs-client": "^1.1.1",
"@types/uuid": "8.3.0",
"@welldone-software/why-did-you-render": "4.0.6",
"abortcontroller-polyfill": "1.4.0",
"angular": "1.6.9",
"hoist-non-react-statics": "3.3.2",
"angular-bindonce": "0.3.1",
"angular-native-dragdrop": "1.2.2",
"angular-route": "1.6.6",
@ -243,6 +242,7 @@
"eventemitter3": "4.0.0",
"fast-text-encoding": "^1.0.0",
"file-saver": "2.0.2",
"hoist-non-react-statics": "3.3.2",
"immutable": "3.8.2",
"is-hotkey": "0.1.6",
"jquery": "3.5.1",

View File

@ -62,7 +62,7 @@
"eslint-config-prettier": "6.11.0",
"eslint-plugin-jsdoc": "28.6.1",
"eslint-plugin-prettier": "3.1.4",
"eslint-plugin-react-hooks": "4.0.5",
"eslint-plugin-react-hooks": "4.1.2",
"execa": "^1.0.0",
"expect-puppeteer": "4.1.1",
"file-loader": "5.0.2",

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

View File

@ -102,7 +102,6 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
};
onFieldConfigChange = (config: FieldConfigSource) => {
console.log(config);
const { panel } = this.props;
panel.updateFieldConfig({

View File

@ -12624,10 +12624,10 @@ eslint-plugin-prettier@3.1.4:
dependencies:
prettier-linter-helpers "^1.0.0"
eslint-plugin-react-hooks@4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.5.tgz#4879003aa38e5d05d0312175beb6e4a1f617bfcf"
integrity sha512-3YLSjoArsE2rUwL8li4Yxx1SUg3DQWp+78N3bcJQGWVZckcp+yeQGsap/MSq05+thJk57o+Ww4PtZukXGL02TQ==
eslint-plugin-react-hooks@4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.2.tgz#2eb53731d11c95826ef7a7272303eabb5c9a271e"
integrity sha512-ykUeqkGyUGgwTtk78C0o8UG2fzwmgJ0qxBGPp2WqRKsTwcLuVf01kTDRAtOsd4u6whX2XOC8749n2vPydP82fg==
eslint-scope@^4.0.3:
version "4.0.3"