diff --git a/designer/client/.eslintrc b/designer/client/.eslintrc index 2cfac147283..ea457e5f913 100644 --- a/designer/client/.eslintrc +++ b/designer/client/.eslintrc @@ -60,7 +60,8 @@ ] } ], - "react/prop-types": "off" + "react/prop-types": "off", + "react-hooks/exhaustive-deps": "error" }, "overrides": [ { diff --git a/designer/client/src/components/RouteLeavingGuard.tsx b/designer/client/src/components/RouteLeavingGuard.tsx index 5e5298630ea..5c8a64c8bf8 100644 --- a/designer/client/src/components/RouteLeavingGuard.tsx +++ b/designer/client/src/components/RouteLeavingGuard.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect } from "react"; import { unsavedProcessChanges } from "../common/DialogMessages"; import { useWindows } from "../windowManager"; -import { unstable_useBlocker as useBlocker } from "react-router-dom"; +import { useBlocker } from "react-router-dom"; export function useRouteLeavingGuard(when: boolean) { const { confirm } = useWindows(); diff --git a/designer/client/src/components/graph/node-modal/editors/expression/Cron/CronEditor.tsx b/designer/client/src/components/graph/node-modal/editors/expression/Cron/CronEditor.tsx index f9e93f0e36a..437fc59d294 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/Cron/CronEditor.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/Cron/CronEditor.tsx @@ -1,5 +1,5 @@ import { ExpressionObj } from "../types"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import Cron from "react-cron-generator"; import "react-cron-generator/dist/cron-builder.css"; import Input from "../../field/Input"; @@ -33,9 +33,12 @@ export const CronEditor: ExtendedEditor = (props: Props) => { const cronFormatter = formatter == null ? typeFormatters[FormatterType.Cron] : formatter; - function encode(value) { - return value == "" ? "" : cronFormatter.encode(value); - } + const encode = useCallback( + (value: string): string => { + return value === "" ? "" : cronFormatter.encode(value); + }, + [cronFormatter], + ); function decode(expression: string): CronExpression { const result = cronFormatter.decode(expression); @@ -65,7 +68,7 @@ export const CronEditor: ExtendedEditor = (props: Props) => { useEffect(() => { onValueChange(encode(value)); - }, [value]); + }, [encode, onValueChange, value]); const onInputFocus = () => { if (!readOnly) { diff --git a/designer/client/src/components/graph/node-modal/editors/expression/SqlEditor.tsx b/designer/client/src/components/graph/node-modal/editors/expression/SqlEditor.tsx index cd5f3caa8cc..9db4b0e26d4 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/SqlEditor.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/SqlEditor.tsx @@ -47,24 +47,25 @@ function useAliasUsageHighlight(token = "alias") { [session, token], ); - const toggleClassname = useCallback( - debounce( - (classname: string, enabled: boolean): void => { - const el = ref.current?.refEditor; - - if (el) { - if (!enabled) { - el.className = el.className.replace(classname, ""); + const toggleClassname = useMemo( + () => + debounce( + (classname: string, enabled: boolean): void => { + const el = ref.current?.refEditor; + + if (el) { + if (!enabled) { + el.className = el.className.replace(classname, ""); + } + + if (!el.className.includes(classname)) { + el.className = cx(el.className, { [classname]: enabled }); + } } - - if (!el.className.includes(classname)) { - el.className = cx(el.className, { [classname]: enabled }); - } - } - }, - 1000, - { trailing: true, leading: true }, - ), + }, + 1000, + { trailing: true, leading: true }, + ), [], ); diff --git a/designer/client/src/components/graph/node-modal/properties.tsx b/designer/client/src/components/graph/node-modal/properties.tsx index 05c85987b54..3c857d07c85 100644 --- a/designer/client/src/components/graph/node-modal/properties.tsx +++ b/designer/client/src/components/graph/node-modal/properties.tsx @@ -30,7 +30,7 @@ export function Properties({ showValidation, }: Props): JSX.Element { const scenarioProperties = useSelector(getScenarioPropertiesConfig); - const scenarioPropertiesConfig = scenarioProperties?.propertiesConfig ?? {}; + const scenarioPropertiesConfig = useMemo(() => scenarioProperties?.propertiesConfig ?? {}, [scenarioProperties?.propertiesConfig]); //fixme move this configuration to some better place? //we sort by name, to have predictable order of properties (should be replaced by defining order in configuration) diff --git a/designer/client/src/components/modals/AdhocTesting/AdhocTestingDialog.tsx b/designer/client/src/components/modals/AdhocTesting/AdhocTestingDialog.tsx index c1e363a00ba..4146d731e54 100644 --- a/designer/client/src/components/modals/AdhocTesting/AdhocTestingDialog.tsx +++ b/designer/client/src/components/modals/AdhocTesting/AdhocTestingDialog.tsx @@ -72,7 +72,7 @@ function AdhocTestingDialog(props: WindowContentProps { try { await action(processName, comment); @@ -37,7 +35,7 @@ export function DeployProcessDialog(props: WindowContentProps processDefinitionData.componentGroups, [processDefinitionData]); const filters = useMemo(() => props.filter?.toLowerCase().split(/\s/).filter(Boolean), [props.filter]); - const stickyNoteToolGroup = useMemo(() => stickyNoteComponentGroup(pristine), [pristine, props, t]); + const stickyNoteToolGroup = useMemo(() => stickyNoteComponentGroup(pristine), [pristine]); const groups = useMemo(() => { const allComponentGroups = stickyNotesSettings.enabled ? concat(componentGroups, stickyNoteToolGroup) : componentGroups; return allComponentGroups.map(filterComponentsByLabel(filters)).filter((g) => g.components.length > 0); diff --git a/designer/client/src/components/toolbars/creator/ToolboxComponentGroup.tsx b/designer/client/src/components/toolbars/creator/ToolboxComponentGroup.tsx index 58f8b67609f..e189577701b 100644 --- a/designer/client/src/components/toolbars/creator/ToolboxComponentGroup.tsx +++ b/designer/client/src/components/toolbars/creator/ToolboxComponentGroup.tsx @@ -17,7 +17,7 @@ function useStateToggleWithReset(resetCondition: boolean, initialState = false): useEffect(() => { if (resetCondition) setFlag(initialState); - }, [resetCondition]); + }, [initialState, resetCondition]); const toggle = useCallback(() => setFlag((state) => !state), []); diff --git a/designer/client/src/components/toolbars/process/buttons/MigrateButton.tsx b/designer/client/src/components/toolbars/process/buttons/MigrateButton.tsx index 0d252a2cb41..0bc842293e1 100644 --- a/designer/client/src/components/toolbars/process/buttons/MigrateButton.tsx +++ b/designer/client/src/components/toolbars/process/buttons/MigrateButton.tsx @@ -39,7 +39,7 @@ function MigrateButton(props: Props) { confirmText: t("panels.actions.process-migrate.yes", "Yes"), denyText: t("panels.actions.process-migrate.no", "No"), }), - [confirm, processName, t, targetEnvironmentId, versionId], + [confirm, dispatch, processName, t, targetEnvironmentId, versionId], ); if (isEmpty(featuresSettings?.remoteEnvironment)) { diff --git a/designer/client/src/components/toolbars/search/AdvancedSearchFilters.tsx b/designer/client/src/components/toolbars/search/AdvancedSearchFilters.tsx index ac33f20ee88..2f438e3d1b1 100644 --- a/designer/client/src/components/toolbars/search/AdvancedSearchFilters.tsx +++ b/designer/client/src/components/toolbars/search/AdvancedSearchFilters.tsx @@ -47,7 +47,7 @@ export function AdvancedSearchFilters({ return new Set( componentsGroups.flatMap((componentGroup) => componentGroup.components).map((component) => component.label.toLowerCase()), ); - }, []); + }, [componentsGroups]); const nodeTypes = useMemo(() => { const availableTypes = allNodes @@ -60,12 +60,12 @@ export function AdvancedSearchFilters({ .filter((type) => componentLabels.has(type.toLowerCase())); return uniq(availableTypes); - }, [allNodes]); + }, [allNodes, componentLabels]); useEffect(() => { const searchQuery = resolveSearchQuery(filter); setFilterFields(searchQuery); - }, [filter]); + }, [filter, setFilterFields]); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); diff --git a/designer/client/src/components/toolbars/search/utils.ts b/designer/client/src/components/toolbars/search/utils.ts index 7045ebfa915..51c741d0e05 100644 --- a/designer/client/src/components/toolbars/search/utils.ts +++ b/designer/client/src/components/toolbars/search/utils.ts @@ -169,7 +169,7 @@ export function useFilteredNodes(searchQuery: SearchQuery): { } }) .filter(({ groups }) => groups.length), - [displayNames, allEdges, searchQuery, allNodes], + [allNodes, allEdges, displayNames, isSimpleSearch, searchQuery], ); } diff --git a/designer/client/src/containers/Notifications.tsx b/designer/client/src/containers/Notifications.tsx index 456fd2a151e..f03e049298f 100644 --- a/designer/client/src/containers/Notifications.tsx +++ b/designer/client/src/containers/Notifications.tsx @@ -111,7 +111,7 @@ export function Notifications(): JSX.Element { ); } }); - }, [currentScenarioName, dispatch, handleChangeConnectionError]); + }, [currentScenarioName, dispatch, handleChangeConnectionError, processVersionId]); useInterval(refresh, { refreshTime: 2000, ignoreFirst: true, diff --git a/designer/client/src/containers/event-tracking/use-error-register.ts b/designer/client/src/containers/event-tracking/use-error-register.ts index fc54325d237..945809a328f 100644 --- a/designer/client/src/containers/event-tracking/use-error-register.ts +++ b/designer/client/src/containers/event-tracking/use-error-register.ts @@ -25,5 +25,5 @@ export const useErrorRegister = () => { }); apm.setCustomContext({ nuApiVersion: buildInfo.version, nuUiVersion: __BUILD_VERSION__ }); - }, [buildInfo?.version]); + }, [areErrorReportsEnabled, buildInfo?.version, environment]); }; diff --git a/designer/client/src/containers/event-tracking/use-event-tracking.tsx b/designer/client/src/containers/event-tracking/use-event-tracking.tsx index aeacdda1679..af2b2ea44ab 100644 --- a/designer/client/src/containers/event-tracking/use-event-tracking.tsx +++ b/designer/client/src/containers/event-tracking/use-event-tracking.tsx @@ -1,6 +1,6 @@ import { debounce } from "lodash"; import httpService from "../../http/HttpService"; -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; import { getEventStatisticName } from "./helpers"; import { useSelector } from "react-redux"; import { getFeatureSettings } from "../../reducers/selectors/settings"; @@ -12,17 +12,17 @@ export const useEventTracking = () => { const featuresSettings = useSelector(getFeatureSettings); const areStatisticsEnabled = featuresSettings.usageStatisticsReports.enabled; - const trackEvent = async (trackEventParams: TrackEventParams) => { - if (!areStatisticsEnabled) { - return; - } - await httpService.sendStatistics([{ name: getEventStatisticName(trackEventParams) }]); - }; - - const trackEventWithDebounce = useCallback( - debounce((event: TrackEventParams) => trackEvent(event), 1500), - [], + const trackEvent = useCallback( + async (trackEventParams: TrackEventParams) => { + if (!areStatisticsEnabled) { + return; + } + await httpService.sendStatistics([{ name: getEventStatisticName(trackEventParams) }]); + }, + [areStatisticsEnabled], ); + const trackEventWithDebounce = useMemo(() => debounce((event: TrackEventParams) => trackEvent(event), 1500), [trackEvent]); + return { trackEvent, trackEventWithDebounce }; };