Browse Source

[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>
Denys Malofeiev 3 years ago
parent
commit
94b1f4a772
39 changed files with 165 additions and 123 deletions
  1. 4 1
      kafka-ui-react-app/.eslintrc.json
  2. 12 6
      kafka-ui-react-app/src/components/Alerts/Alerts.tsx
  3. 2 2
      kafka-ui-react-app/src/components/App.tsx
  4. 1 1
      kafka-ui-react-app/src/components/Brokers/Brokers.tsx
  5. 6 1
      kafka-ui-react-app/src/components/Cluster/Cluster.tsx
  6. 1 1
      kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx
  7. 1 1
      kafka-ui-react-app/src/components/Connect/Edit/Edit.tsx
  8. 1 1
      kafka-ui-react-app/src/components/Connect/List/ListItem.tsx
  9. 1 1
      kafka-ui-react-app/src/components/Connect/New/New.tsx
  10. 1 1
      kafka-ui-react-app/src/components/ConsumerGroups/ConsumerGroups.tsx
  11. 2 2
      kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx
  12. 4 3
      kafka-ui-react-app/src/components/ConsumerGroups/Details/ResetOffsets/ResetOffsets.tsx
  13. 1 1
      kafka-ui-react-app/src/components/KsqlDb/List/List.tsx
  14. 17 14
      kafka-ui-react-app/src/components/KsqlDb/Query/Query.tsx
  15. 2 1
      kafka-ui-react-app/src/components/KsqlDb/Query/ResultRenderer.tsx
  16. 3 3
      kafka-ui-react-app/src/components/Schemas/Details/Details.tsx
  17. 48 36
      kafka-ui-react-app/src/components/Schemas/Edit/Edit.tsx
  18. 1 1
      kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector.tsx
  19. 1 1
      kafka-ui-react-app/src/components/Schemas/List/List.tsx
  20. 1 1
      kafka-ui-react-app/src/components/Schemas/New/New.tsx
  21. 4 4
      kafka-ui-react-app/src/components/Topics/List/List.tsx
  22. 2 2
      kafka-ui-react-app/src/components/Topics/List/ListItem.tsx
  23. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx
  24. 3 3
      kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx
  25. 15 5
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.tsx
  26. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessagesTable.tsx
  27. 2 5
      kafka-ui-react-app/src/components/Topics/Topic/Edit/Edit.tsx
  28. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx
  29. 1 1
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamField.tsx
  30. 1 0
      kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.route.tsx
  31. 1 1
      kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.tsx
  32. 1 1
      kafka-ui-react-app/src/components/common/BytesFormatted/BytesFormatted.tsx
  33. 2 2
      kafka-ui-react-app/src/components/common/ConfirmationModal/ConfirmationModal.tsx
  34. 1 1
      kafka-ui-react-app/src/components/common/Dropdown/Dropdown.tsx
  35. 5 5
      kafka-ui-react-app/src/components/common/DynamicTextButton/DynamicTextButton.tsx
  36. 1 1
      kafka-ui-react-app/src/components/common/Pagination/Pagination.tsx
  37. 7 4
      kafka-ui-react-app/src/components/common/Tabs/Tabs.tsx
  38. 4 4
      kafka-ui-react-app/src/lib/hooks/__tests__/useDataSaver.spec.tsx
  39. 2 2
      kafka-ui-react-app/src/lib/hooks/useSearch.ts

+ 4 - 1
kafka-ui-react-app/.eslintrc.json

@@ -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",

+ 12 - 6
kafka-ui-react-app/src/components/Alerts/Alerts.tsx

@@ -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) => {
-    dispatch(alertDissmissed(id));
-  }, []);
+  const dismiss = React.useCallback(
+    (id: string) => {
+      dispatch(alertDissmissed(id));
+    },
+    [dispatch]
+  );
 
   const legacyAlerts = useAppSelector(getAlerts);
-  const dismissLegacy = React.useCallback((id: string) => {
-    dispatch(dismissAlert(id));
-  }, []);
+  const dismissLegacy = React.useCallback(
+    (id: string) => {
+      dispatch(dismissAlert(id));
+    },
+    [dispatch]
+  );
 
   return (
     <>

+ 2 - 2
kafka-ui-react-app/src/components/App.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Brokers/Brokers.tsx

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

+ 6 - 1
kafka-ui-react-app/src/components/Cluster/Cluster.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Connect/Details/Actions/Actions.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Connect/Edit/Edit.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Connect/List/ListItem.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Connect/New/New.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/ConsumerGroups/ConsumerGroups.tsx

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

+ 2 - 2
kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx

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

+ 4 - 3
kafka-ui-react-app/src/components/ConsumerGroups/Details/ResetOffsets/ResetOffsets.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/KsqlDb/List/List.tsx

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

+ 17 - 14
kafka-ui-react-app/src/components/KsqlDb/Query/Query.tsx

@@ -42,7 +42,7 @@ const Query: FC = () => {
 
   useEffect(() => {
     return reset;
-  }, []);
+  }, [reset]);
 
   const { handleSubmit, setValue, control } = useForm<FormValues>({
     mode: 'onTouched',
@@ -53,19 +53,22 @@ const Query: FC = () => {
     },
   });
 
-  const submitHandler = useCallback(async (values: FormValues) => {
-    dispatch(
-      executeKsql({
-        clusterName,
-        ksqlCommand: {
-          ...values,
-          streamsProperties: values.streamsProperties
-            ? JSON.parse(values.streamsProperties)
-            : undefined,
-        },
-      })
-    );
-  }, []);
+  const submitHandler = useCallback(
+    async (values: FormValues) => {
+      dispatch(
+        executeKsql({
+          clusterName,
+          ksqlCommand: {
+            ...values,
+            streamsProperties: values.streamsProperties
+              ? JSON.parse(values.streamsProperties)
+              : undefined,
+          },
+        })
+      );
+    },
+    [clusterName, dispatch]
+  );
 
   return (
     <>

+ 2 - 1
kafka-ui-react-app/src/components/KsqlDb/Query/ResultRenderer.tsx

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

+ 3 - 3
kafka-ui-react-app/src/components/Schemas/Details/Details.tsx

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

+ 48 - 36
kafka-ui-react-app/src/components/Schemas/Edit/Edit.tsx

@@ -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,44 +58,56 @@ const Edit: React.FC = () => {
       : JSON.stringify(JSON.parse(schema?.schema || '{}'), null, '\t');
   }, [schema]);
 
-  const onSubmit = React.useCallback(async (props: NewSchemaSubjectRaw) => {
-    if (!schema) return;
+  const onSubmit = React.useCallback(
+    async (props: NewSchemaSubjectRaw) => {
+      if (!schema) return;
 
-    try {
-      if (dirtyFields.newSchema || dirtyFields.schemaType) {
-        const resp = await schemasApiClient.createNewSchema({
-          clusterName,
-          newSchemaSubject: {
-            ...schema,
-            schema: props.newSchema || schema.schema,
-            schemaType: props.schemaType || schema.schemaType,
-          },
-        });
-        dispatch(schemaAdded(resp));
-      }
+      try {
+        if (dirtyFields.newSchema || dirtyFields.schemaType) {
+          const resp = await schemasApiClient.createNewSchema({
+            clusterName,
+            newSchemaSubject: {
+              ...schema,
+              schema: props.newSchema || schema.schema,
+              schemaType: props.schemaType || schema.schemaType,
+            },
+          });
+          dispatch(schemaAdded(resp));
+        }
 
-      if (dirtyFields.compatibilityLevel) {
-        await schemasApiClient.updateSchemaCompatibilityLevel({
-          clusterName,
-          subject,
-          compatibilityLevel: {
-            compatibility: props.compatibilityLevel,
-          },
-        });
-        dispatch(
-          schemaUpdated({
-            ...schema,
-            compatibilityLevel: props.compatibilityLevel,
-          })
-        );
-      }
+        if (dirtyFields.compatibilityLevel) {
+          await schemasApiClient.updateSchemaCompatibilityLevel({
+            clusterName,
+            subject,
+            compatibilityLevel: {
+              compatibility: props.compatibilityLevel,
+            },
+          });
+          dispatch(
+            schemaUpdated({
+              ...schema,
+              compatibilityLevel: props.compatibilityLevel,
+            })
+          );
+        }
 
-      history.push(clusterSchemaPath(clusterName, subject));
-    } catch (e) {
-      const err = await getResponse(e as Response);
-      dispatch(serverErrorAlertAdded(err));
-    }
-  }, []);
+        history.push(clusterSchemaPath(clusterName, subject));
+      } catch (e) {
+        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 />;

+ 1 - 1
kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Schemas/List/List.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Schemas/New/New.tsx

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

+ 4 - 4
kafka-ui-react-app/src/components/Topics/List/List.tsx

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

+ 2 - 2
kafka-ui-react-app/src/components/Topics/List/ListItem.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx

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

+ 3 - 3
kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx

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

+ 15 - 5
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessagesTable.tsx

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

+ 2 - 5
kafka-ui-react-app/src/components/Topics/Topic/Edit/Edit.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/SendMessage/SendMessage.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamField.tsx

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

+ 1 - 0
kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.route.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/common/BytesFormatted/BytesFormatted.tsx

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

+ 2 - 2
kafka-ui-react-app/src/components/common/ConfirmationModal/ConfirmationModal.tsx

@@ -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" />

+ 1 - 1
kafka-ui-react-app/src/components/common/Dropdown/Dropdown.tsx

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

+ 5 - 5
kafka-ui-react-app/src/components/common/DynamicTextButton/DynamicTextButton.tsx

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

+ 1 - 1
kafka-ui-react-app/src/components/common/Pagination/Pagination.tsx

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

+ 7 - 4
kafka-ui-react-app/src/components/common/Tabs/Tabs.tsx

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

+ 4 - 4
kafka-ui-react-app/src/lib/hooks/__tests__/useDataSaver.spec.tsx

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

+ 2 - 2
kafka-ui-react-app/src/lib/hooks/useSearch.ts

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