[Fixed issue/1587] Got rid of react-hooks/exhaustive-deps errors (#1616)

* got rid of react-hooks/exhaustive-deps errors - part 1

* got rid of react-hooks/exhaustive-deps errors in useSearch

* got rid of react-hooks/exhaustive-deps errors in Filters

* got rid of react-hooks/exhaustive-deps errors in ResetOffsets

* got rid of react-hooks/exhaustive-deps errors in Filters

* got rid of react-hooks/exhaustive-deps errors in Breadcrumbs

* got rid of react-hooks/exhaustive-deps errors in DynamicTextButton

* got rid of react-hooks/exhaustive-deps errors in useDataSaver

* got rid of react-hooks/exhaustive-deps errors in ResultRenderer

Co-authored-by: Roman Zabaluev <rzabaluev@provectus.com>
This commit is contained in:
Denys Malofeiev 2022-02-21 14:03:56 +02:00 committed by GitHub
parent 3f0693bad6
commit 94b1f4a772
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 166 additions and 124 deletions

View file

@ -22,7 +22,8 @@
},
"plugins": [
"@typescript-eslint",
"prettier"
"prettier",
"eslint-plugin-react-hooks"
],
"extends": [
"airbnb",
@ -33,6 +34,8 @@
"prettier"
],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react/no-unused-prop-types": "off",
"react/require-default-props": "off",
"prettier/prettier": "warn",

View file

@ -8,14 +8,20 @@ import Alert from 'components/Alerts/Alert';
const Alerts: React.FC = () => {
const alerts = useAppSelector(selectAll);
const dispatch = useAppDispatch();
const dismiss = React.useCallback((id: string) => {
const dismiss = React.useCallback(
(id: string) => {
dispatch(alertDissmissed(id));
}, []);
},
[dispatch]
);
const legacyAlerts = useAppSelector(getAlerts);
const dismissLegacy = React.useCallback((id: string) => {
const dismissLegacy = React.useCallback(
(id: string) => {
dispatch(dismissAlert(id));
}, []);
},
[dispatch]
);
return (
<>

View file

@ -36,11 +36,11 @@ const App: React.FC = () => {
React.useEffect(() => {
closeSidebar();
}, [location]);
}, [closeSidebar, location]);
React.useEffect(() => {
dispatch(fetchClusters());
}, [fetchClusters]);
}, [dispatch]);
return (
<ThemeProvider theme={theme}>

View file

@ -35,7 +35,7 @@ const Brokers: React.FC = () => {
React.useEffect(() => {
dispatch(fetchClusterStats(clusterName));
}, [fetchClusterStats, clusterName]);
}, [clusterName, dispatch]);
useInterval(() => {
fetchClusterStats(clusterName);

View file

@ -49,7 +49,12 @@ const Cluster: React.FC = () => {
hasSchemaRegistryConfigured,
isTopicDeletionAllowed,
}),
[features]
[
hasKafkaConnectConfigured,
hasSchemaRegistryConfigured,
isReadOnly,
isTopicDeletionAllowed,
]
);
return (

View file

@ -70,7 +70,7 @@ const Actions: React.FC<ActionsProps> = ({
} catch {
// do not redirect
}
}, [deleteConnector, clusterName, connectName, connectorName]);
}, [deleteConnector, clusterName, connectName, connectorName, history]);
const restartConnectorHandler = React.useCallback(() => {
restartConnector(clusterName, connectName, connectorName);

View file

@ -100,7 +100,7 @@ const Edit: React.FC<EditProps> = ({
);
}
},
[updateConfig, clusterName, connectName, connectorName]
[updateConfig, clusterName, connectName, connectorName, history]
);
if (isConfigFetching) return <PageLoader />;

View file

@ -45,7 +45,7 @@ const ListItem: React.FC<ListItemProps> = ({
dispatch(deleteConnector(clusterName, connect, name));
}
setDeleteConnectorConfirmationVisible(false);
}, [clusterName, connect, name]);
}, [clusterName, connect, dispatch, name]);
const runningTasks = React.useMemo(() => {
if (!tasksCount) return null;

View file

@ -101,7 +101,7 @@ const New: React.FC<NewProps> = ({
);
}
},
[createConnector, clusterName]
[createConnector, clusterName, history]
);
if (areConnectsFetching) {

View file

@ -18,7 +18,7 @@ const ConsumerGroups: React.FC = () => {
const isFetched = useAppSelector(getAreConsumerGroupsFulfilled);
React.useEffect(() => {
dispatch(fetchConsumerGroups(clusterName));
}, [fetchConsumerGroups, clusterName]);
}, [clusterName, dispatch]);
if (isFetched) {
return (

View file

@ -47,7 +47,7 @@ const Details: React.FC = () => {
React.useEffect(() => {
dispatch(fetchConsumerGroupDetails({ clusterName, consumerGroupID }));
}, [fetchConsumerGroupDetails, clusterName, consumerGroupID]);
}, [clusterName, consumerGroupID, dispatch]);
const onDelete = () => {
setIsConfirmationModalVisible(false);
@ -57,7 +57,7 @@ const Details: React.FC = () => {
if (isDeleted) {
history.push(clusterConsumerGroupsPath(clusterName));
}
}, [isDeleted]);
}, [clusterName, history, isDeleted]);
const onResetOffsets = () => {
history.push(

View file

@ -59,7 +59,7 @@ const ResetOffsets: React.FC = () => {
React.useEffect(() => {
dispatch(fetchConsumerGroupDetails({ clusterName, consumerGroupID }));
}, [clusterName, consumerGroupID]);
}, [clusterName, consumerGroupID, dispatch]);
const [uniqueTopics, setUniqueTopics] = React.useState<string[]>([]);
const [selectedPartitions, setSelectedPartitions] = React.useState<Option[]>(
@ -96,7 +96,7 @@ const ResetOffsets: React.FC = () => {
setValue('topic', consumerGroup.partitions[0].topic);
setUniqueTopics(Object.keys(groupBy(consumerGroup.partitions, 'topic')));
}
}, [isFetched]);
}, [consumerGroup?.partitions, isFetched, setValue]);
const onSelectedPartitionsChange = (value: Option[]) => {
clearErrors();
@ -117,6 +117,7 @@ const ResetOffsets: React.FC = () => {
React.useEffect(() => {
onSelectedPartitionsChange([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [topicValue]);
const onSubmit = (data: FormType) => {
@ -169,7 +170,7 @@ const ResetOffsets: React.FC = () => {
clusterConsumerGroupDetailsPath(clusterName, consumerGroupID)
);
}
}, [isOffsetReseted]);
}, [clusterName, consumerGroupID, dispatch, history, isOffsetReseted]);
if (!isFetched || !consumerGroup) {
return <PageLoader />;

View file

@ -32,7 +32,7 @@ const List: FC = () => {
useEffect(() => {
dispatch(fetchKsqlDbTables(clusterName));
}, []);
}, [clusterName, dispatch]);
return (
<>

View file

@ -42,7 +42,7 @@ const Query: FC = () => {
useEffect(() => {
return reset;
}, []);
}, [reset]);
const { handleSubmit, setValue, control } = useForm<FormValues>({
mode: 'onTouched',
@ -53,7 +53,8 @@ const Query: FC = () => {
},
});
const submitHandler = useCallback(async (values: FormValues) => {
const submitHandler = useCallback(
async (values: FormValues) => {
dispatch(
executeKsql({
clusterName,
@ -65,7 +66,9 @@ const Query: FC = () => {
},
})
);
}, []);
},
[clusterName, dispatch]
);
return (
<>

View file

@ -20,6 +20,7 @@ const ResultRenderer: React.FC<{ result: KsqlCommandResponse | null }> = ({
const { headers, rows } = rawTable;
// eslint-disable-next-line react-hooks/rules-of-hooks
const transformedRows = React.useMemo(
() =>
rows.map((row) =>
@ -31,7 +32,7 @@ const ResultRenderer: React.FC<{ result: KsqlCommandResponse | null }> = ({
{} as Dictionary<string>
)
),
[]
[rawTable.headers, rows]
);
return (

View file

@ -47,14 +47,14 @@ const Details: React.FC = () => {
return () => {
dispatch(resetLoaderById(SCHEMA_LATEST_FETCH_ACTION));
};
}, []);
}, [clusterName, dispatch, subject]);
React.useEffect(() => {
dispatch(fetchSchemaVersions({ clusterName, subject }));
return () => {
dispatch(resetLoaderById(SCHEMAS_VERSIONS_FETCH_ACTION));
};
}, [clusterName, subject]);
}, [clusterName, dispatch, subject]);
const versions = useAppSelector((state) => selectAllSchemaVersions(state));
const schema = useAppSelector(getSchemaLatest);
@ -72,7 +72,7 @@ const Details: React.FC = () => {
const err = await getResponse(e as Response);
dispatch(serverErrorAlertAdded(err));
}
}, [clusterName, subject]);
}, [clusterName, dispatch, history, subject]);
if (!isFetched || !schema) {
return <PageLoader />;

View file

@ -47,7 +47,7 @@ const Edit: React.FC = () => {
return () => {
dispatch(resetLoaderById(SCHEMA_LATEST_FETCH_ACTION));
};
}, [clusterName, subject]);
}, [clusterName, dispatch, subject]);
const schema = useAppSelector((state) => getSchemaLatest(state));
const isFetched = useAppSelector(getAreSchemaLatestFulfilled);
@ -58,7 +58,8 @@ const Edit: React.FC = () => {
: JSON.stringify(JSON.parse(schema?.schema || '{}'), null, '\t');
}, [schema]);
const onSubmit = React.useCallback(async (props: NewSchemaSubjectRaw) => {
const onSubmit = React.useCallback(
async (props: NewSchemaSubjectRaw) => {
if (!schema) return;
try {
@ -95,7 +96,18 @@ const Edit: React.FC = () => {
const err = await getResponse(e as Response);
dispatch(serverErrorAlertAdded(err));
}
}, []);
},
[
clusterName,
dirtyFields.compatibilityLevel,
dirtyFields.newSchema,
dirtyFields.schemaType,
dispatch,
history,
schema,
subject,
]
);
if (!isFetched || !schema) {
return <PageLoader />;

View file

@ -48,7 +48,7 @@ const GlobalSchemaSelector: React.FC = () => {
};
fetchData();
}, []);
}, [clusterName]);
const handleChangeCompatibilityLevel = (level: string | number) => {
setNextCompatibilityLevel(level as CompatibilityLevelCompatibilityEnum);

View file

@ -41,7 +41,7 @@ const List: React.FC = () => {
return () => {
dispatch(resetLoaderById(SCHEMAS_FETCH_ACTION));
};
}, [clusterName, page, perPage, searchText]);
}, [clusterName, dispatch, page, perPage, searchText]);
return (
<>

View file

@ -55,7 +55,7 @@ const New: React.FC = () => {
dispatch(serverErrorAlertAdded(err));
}
},
[clusterName]
[clusterName, dispatch, history]
);
return (

View file

@ -93,7 +93,7 @@ const List: React.FC<TopicsListProps> = ({
const handleSwitch = React.useCallback(() => {
setShowInternal(!showInternal);
history.push(`${pathname}?page=1&perPage=${perPage || PER_PAGE}`);
}, [showInternal]);
}, [history, pathname, perPage, showInternal]);
const [confirmationModal, setConfirmationModal] = React.useState<
'' | 'deleteTopics' | 'purgeMessages'
@ -127,18 +127,18 @@ const List: React.FC<TopicsListProps> = ({
deleteTopics(clusterName, Array.from(selectedTopics));
closeConfirmationModal();
clearSelectedTopics();
}, [clusterName, selectedTopics]);
}, [clusterName, deleteTopics, selectedTopics]);
const purgeMessagesHandler = React.useCallback(() => {
clearTopicsMessages(clusterName, Array.from(selectedTopics));
closeConfirmationModal();
clearSelectedTopics();
}, [clusterName, selectedTopics]);
}, [clearTopicsMessages, clusterName, selectedTopics]);
const searchHandler = React.useCallback(
(searchString: string) => {
setTopicsSearch(searchString);
history.push(`${pathname}?page=1&perPage=${perPage || PER_PAGE}`);
},
[search, pathname, perPage]
[setTopicsSearch, history, pathname, perPage]
);
return (

View file

@ -70,11 +70,11 @@ const ListItem: React.FC<ListItemProps> = ({
const deleteTopicHandler = React.useCallback(() => {
deleteTopic(clusterName, name);
}, [clusterName, name]);
}, [clusterName, deleteTopic, name]);
const clearTopicMessagesHandler = React.useCallback(() => {
clearTopicMessages(clusterName, name);
}, [clusterName, name]);
}, [clearTopicMessages, clusterName, name]);
const [vElipsisVisble, setVElipsisVisble] = React.useState(false);
return (

View file

@ -27,7 +27,7 @@ const TopicConsumerGroups: React.FC<Props> = ({
}) => {
React.useEffect(() => {
fetchTopicConsumerGroups(clusterName, topicName);
}, []);
}, [clusterName, fetchTopicConsumerGroups, topicName]);
return (
<div>

View file

@ -64,19 +64,19 @@ const Details: React.FC<Props> = ({
React.useState(false);
const deleteTopicHandler = React.useCallback(() => {
deleteTopic(clusterName, topicName);
}, [clusterName, topicName]);
}, [clusterName, deleteTopic, topicName]);
React.useEffect(() => {
if (isDeleted) {
dispatch(deleteTopicAction.cancel());
history.push(clusterTopicsPath(clusterName));
}
}, [isDeleted]);
}, [clusterName, dispatch, history, isDeleted]);
const clearTopicMessagesHandler = React.useCallback(() => {
clearTopicMessages(clusterName, topicName);
setClearTopicConfirmationVisible(false);
}, [clusterName, topicName]);
}, [clearTopicMessages, clusterName, topicName]);
return (
<div>

View file

@ -126,7 +126,7 @@ const Filters: React.FC<FiltersProps> = ({
[partitions]
);
const handleFiltersSubmit = () => {
const handleFiltersSubmit = React.useCallback(() => {
setAttempt(attempt + 1);
const props: Query = {
@ -166,7 +166,8 @@ const Filters: React.FC<FiltersProps> = ({
history.push({
search: `?${qs}`,
});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const toggleSeekDirection = (val: string) => {
const nextSeekDirectionValue =
@ -224,17 +225,26 @@ const Filters: React.FC<FiltersProps> = ({
sse.close();
};
}
}, [clusterName, topicName, location]);
}, [
clusterName,
topicName,
location,
setIsFetching,
resetMessages,
addMessage,
updatePhase,
updateMeta,
]);
React.useEffect(() => {
if (location.search.length === 0) {
handleFiltersSubmit();
}
}, [location]);
}, [handleFiltersSubmit, location]);
React.useEffect(() => {
handleFiltersSubmit();
}, [seekDirection]);
}, [handleFiltersSubmit, seekDirection]);
return (
<S.FiltersWrapper>

View file

@ -27,7 +27,7 @@ const MessagesTable: React.FC = () => {
const searchParams = React.useMemo(
() => new URLSearchParams(location.search),
[location, history]
[location]
);
const messages = useSelector(getTopicMessges);

View file

@ -88,10 +88,7 @@ const Edit: React.FC<Props> = ({
fetchTopicConfig,
updateTopic,
}) => {
const defaultValues = React.useMemo(
() => topicParams(topic),
[topicParams, topic]
);
const defaultValues = React.useMemo(() => topicParams(topic), [topic]);
const methods = useForm<TopicFormData>({
defaultValues,
resolver: yupResolver(topicFormValidationSchema),
@ -109,7 +106,7 @@ const Edit: React.FC<Props> = ({
const { name } = methods.getValues();
history.push(clusterTopicPath(clusterName, name));
}
}, [isSubmitting, isTopicUpdated, clusterTopicPath, clusterName, methods]);
}, [isSubmitting, isTopicUpdated, clusterName, methods, history]);
if (!isFetched || !topic || !topic.config) {
return null;

View file

@ -41,7 +41,7 @@ const SendMessage: React.FC = () => {
React.useEffect(() => {
dispatch(fetchTopicMessageSchema(clusterName, topicName));
}, []);
}, [clusterName, dispatch, topicName]);
const messageSchema = useAppSelector((state) =>
getMessageSchemaByTopicName(state, topicName)

View file

@ -54,7 +54,7 @@ const CustomParamField: React.FC<Props> = ({
shouldValidate: true,
});
}
}, [nameValue]);
}, [existingFields, index, nameValue, setExistingFields, setValue]);
return (
<C.Column>

View file

@ -15,6 +15,7 @@ const BreadcrumbRouteInternal: React.FC = () => {
useEffect(() => {
context.handleRouteChange({ ...match, url: location.pathname });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.pathname]);
return null;

View file

@ -13,7 +13,7 @@ const Breadcrumb: React.FC = () => {
const links = React.useMemo(
() => breadcrumbContext.path.slice(basePathEntriesLength),
[breadcrumbContext.link]
[breadcrumbContext.path]
);
const getPathPredicate = React.useCallback(

View file

@ -21,7 +21,7 @@ const BytesFormatted: React.FC<Props> = ({ value, precision = 0 }) => {
} catch (e) {
return `-Bytes`;
}
}, [value]);
}, [precision, value]);
return <span>{formatedValue}</span>;
};

View file

@ -19,14 +19,14 @@ const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
onConfirm,
isConfirming = false,
}) => {
if (!isOpen) return null;
const cancelHandler = React.useCallback(() => {
if (!isConfirming) {
onCancel();
}
}, [isConfirming, onCancel]);
if (!isOpen) return null;
return (
<ConfirmationModalWrapper>
<div onClick={cancelHandler} aria-hidden="true" />

View file

@ -22,7 +22,7 @@ const Dropdown: React.FC<DropdownProps> = ({ label, right, up, children }) => {
'is-right': right,
'is-up': up,
}),
[active]
[active, right, up]
);
return (
<div className={classNames} ref={wrapperRef}>

View file

@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useRef } from 'react';
import cx from 'classnames';
interface DynamicTextButtonProps {
@ -18,15 +18,15 @@ const DynamicTextButton: React.FC<DynamicTextButtonProps> = ({
}) => {
const [clicked, setClicked] = React.useState(false);
let timeout: number;
const timeout = useRef(0);
const clickHandler = useCallback(() => {
onClick();
setClicked(true);
timeout = window.setTimeout(() => setClicked(false), delay);
}, []);
timeout.current = window.setTimeout(() => setClicked(false), delay);
}, [delay, onClick]);
React.useEffect(() => () => window.clearTimeout(timeout), []);
React.useEffect(() => () => window.clearTimeout(timeout.current));
return (
<button

View file

@ -68,7 +68,7 @@ const Pagination: React.FC<PaginationProps> = ({ totalPages }) => {
}
return p;
}, []);
}, [currentPage, totalPages]);
return (
<S.Wrapper role="navigation" aria-label="pagination">

View file

@ -21,10 +21,13 @@ const Tabs: React.FC<TabsProps> = ({
setSelectedIndex(defaultSelectedIndex);
}, [defaultSelectedIndex]);
const handleChange = React.useCallback((index: number) => {
const handleChange = React.useCallback(
(index: number) => {
setSelectedIndex(index);
onChange?.(index);
}, []);
},
[onChange]
);
return (
<>

View file

@ -25,7 +25,7 @@ describe('useDataSaver hook', () => {
const HookWrapper: React.FC = () => {
const { saveFile } = useDataSaver('message', content);
useEffect(() => saveFile(), []);
useEffect(() => saveFile(), [saveFile]);
return null;
};
@ -52,7 +52,7 @@ describe('useDataSaver hook', () => {
const HookWrapper: React.FC = () => {
const { saveFile } = useDataSaver('message', 'content');
useEffect(() => saveFile(), []);
useEffect(() => saveFile(), [saveFile]);
return null;
};
@ -81,7 +81,7 @@ describe('useDataSaver hook', () => {
it('data with type Object', () => {
const HookWrapper: React.FC = () => {
const { copyToClipboard } = useDataSaver('topic', content);
useEffect(() => copyToClipboard(), []);
useEffect(() => copyToClipboard(), [copyToClipboard]);
return null;
};
render(<HookWrapper />);
@ -96,7 +96,7 @@ describe('useDataSaver hook', () => {
'topic',
'{ title: "title", }'
);
useEffect(() => copyToClipboard(), []);
useEffect(() => copyToClipboard(), [copyToClipboard]);
return null;
};
render(<HookWrapper />);

View file

@ -21,7 +21,7 @@ const useSearch = (initValue = ''): [string, (value: string) => void] => {
queryParams.set(SEARCH_QUERY_ARG, initValue.trim());
history.push({ pathname, search: queryParams.toString() });
}
}, []);
}, [history, initValue, pathname, q, queryParams]);
const handleChange = useCallback(
(value: string) => {
@ -39,7 +39,7 @@ const useSearch = (initValue = ''): [string, (value: string) => void] => {
history.replace({ pathname, search: queryParams.toString() });
}
},
[history, pathname, queryParams, q]
[q, page, history, pathname, queryParams]
);
return [q || initValue.trim() || '', handleChange];