diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 33a64939e0f..f4084d1a6db 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -108,7 +108,6 @@ import { setUserTablePreference } from "@app/helpers/userTablePreferences"; import { - useDebounce, useLocalStorageState, usePagination, usePopUp, @@ -287,7 +286,6 @@ const OverviewPageContent = () => { const isProjectV3 = currentProject?.version === ProjectVersion.V3; const projectSlug = currentProject?.slug as string; const [searchFilter, setSearchFilter] = useState(""); - const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(searchFilter); const secretPath = (routerSearch?.secretPath as string) || "/"; const { subscription } = useSubscription(); const { hasOrgRole } = useOrgPermission(); @@ -555,7 +553,7 @@ const OverviewPageContent = () => { includeSecrets: activeTagSlugs.length > 0 || (isFilteredByResources ? filter.secret : true), includeImports: isFilteredByResources ? (filter[RowType.SecretImport] ?? true) : true, includeSecretRotations: isFilteredByResources ? filter.rotation : true, - search: debouncedSearchFilter, + search: searchFilter, tags: tagFilter, limit, offset @@ -795,7 +793,6 @@ const OverviewPageContent = () => { if (search) { setSearchFilter(search as string); - setDebouncedSearchFilter(search as string); } } }, [routerSearch.search, routerSearch.filterBy]); @@ -1753,7 +1750,7 @@ const OverviewPageContent = () => { if (isFilteredByResources && !filter.secret && !activeTagSlugs.length) return secKeys; const result = [...secKeys]; - const searchLower = debouncedSearchFilter.toLowerCase(); + const searchLower = searchFilter.toLowerCase(); pendingChanges.secrets.forEach((change) => { if (change.type === PendingAction.Create && !result.includes(change.secretKey)) { if (!searchLower || change.secretKey.toLowerCase().includes(searchLower)) { @@ -1766,7 +1763,7 @@ const OverviewPageContent = () => { secKeys, isBatchModeActive, pendingChanges.secrets, - debouncedSearchFilter, + searchFilter, isFilteredByResources, filter.secret ]); @@ -1862,7 +1859,7 @@ const OverviewPageContent = () => { // If resource filter is active and folders are excluded, skip pending folder creates const includePendingFolderCreates = !isFilteredByResources || filter.folder; - const searchLower = debouncedSearchFilter.toLowerCase(); + const searchLower = searchFilter.toLowerCase(); pendingChanges.folders.forEach((change) => { if (change.type === PendingAction.Create) { if ( @@ -1902,7 +1899,7 @@ const OverviewPageContent = () => { folderNamesAndDescriptions, isBatchModeActive, pendingChanges.folders, - debouncedSearchFilter, + searchFilter, isFilteredByResources, filter.folder ]); @@ -2032,7 +2029,6 @@ const OverviewPageContent = () => { setFilter(restore?.filter ?? DEFAULT_FILTER_STATE); const el = restore?.searchFilter ?? ""; setSearchFilter(el); - setDebouncedSearchFilter(el); }; const handleFolderClick = (path: string) => { @@ -2053,7 +2049,6 @@ const OverviewPageContent = () => { }).then(() => { setFilter(DEFAULT_FILTER_STATE); setSearchFilter(""); - setDebouncedSearchFilter(""); }); }; @@ -2321,7 +2316,7 @@ const OverviewPageContent = () => { ProjectPermissionSecretActions.Create, ProjectPermissionSub.Secrets ); - if (isTableFiltered || debouncedSearchFilter || cannotCreate) return "filter-empty" as const; + if (isTableFiltered || searchFilter || cannotCreate) return "filter-empty" as const; return "add-first-secret" as const; } return "table" as const; @@ -2437,6 +2432,7 @@ const OverviewPageContent = () => { /> )} { ) : null)} {tableView === "tag-filter-empty" && } {tableView === "filter-empty" && ( - + )} {tableView === "add-first-secret" && (
@@ -2905,7 +2899,7 @@ const OverviewPageContent = () => { getSecretImportByEnv={getSecretImportByEnv} tableWidth={tableWidth} secretPath={secretPath} - searchFilter={debouncedSearchFilter} + searchFilter={searchFilter} onDelete={(secretImport) => handlePopUpOpen("deleteSecretImport", secretImport) } @@ -2926,7 +2920,7 @@ const OverviewPageContent = () => { getSecretImportByEnv={getSecretImportByEnv} tableWidth={tableWidth} secretPath={secretPath} - searchFilter={debouncedSearchFilter} + searchFilter={searchFilter} onDelete={(secretImport) => handlePopUpOpen("deleteSecretImport", secretImport) } diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/ResourceSearchInput/ResourceSearchInput.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/ResourceSearchInput/ResourceSearchInput.tsx index bff3db5d38c..57b3eaf58b4 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/ResourceSearchInput/ResourceSearchInput.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/ResourceSearchInput/ResourceSearchInput.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { GlobeIcon, SearchIcon, XIcon } from "lucide-react"; import { @@ -14,6 +14,7 @@ import { TooltipContent, TooltipTrigger } from "@app/components/v3"; +import { useDebounce } from "@app/hooks"; import { QuickSearchModal, QuickSearchModalProps } from "../SecretSearchInput/components"; @@ -24,7 +25,7 @@ type Props = Omit(null); const inputRef = useRef(null); - const hasSearch = Boolean(value.trim()); + + // local input state so typing doesn't re-render the whole table + const [inputValue, setInputValue] = useState(externalValue); + const [debouncedInputValue] = useDebounce(inputValue); + const lastEmittedValue = useRef(externalValue); + + useEffect(() => { + if (externalValue !== lastEmittedValue.current) { + setInputValue(externalValue); + lastEmittedValue.current = externalValue; + } + }, [externalValue]); + + useEffect(() => { + if (debouncedInputValue !== lastEmittedValue.current) { + lastEmittedValue.current = debouncedInputValue; + onChange(debouncedInputValue); + } + }, [debouncedInputValue, onChange]); + + const handleClear = () => { + setInputValue(""); + lastEmittedValue.current = ""; + onChange(""); + }; + + const hasSearch = Boolean(inputValue.trim()); return ( <> @@ -60,9 +87,9 @@ export const ResourceSearchInput = ({ ? "Search by secret, folder, tag or metadata..." : "Search by secret or folder name..." } - value={value} + value={inputValue} onChange={(e) => { - onChange(e.target.value); + setInputValue(e.target.value); setIsOptionHighlighted(false); }} onFocus={() => setIsFocused(true)} @@ -90,7 +117,7 @@ export const ResourceSearchInput = ({ /> {hasSearch && ( - onChange("")}> + @@ -115,7 +142,9 @@ export const ResourceSearchInput = ({ }} > - Search all folders for "{value.trim()}" + + Search all folders for "{inputValue.trim()}" + @@ -124,10 +153,10 @@ export const ResourceSearchInput = ({ isSingleEnv={isSingleEnv} isOpen={isOpen} onOpenChange={setIsOpen} - initialValue={value} + initialValue={inputValue} onClose={() => { setIsOpen(false); - onChange(""); + handleClear(); }} {...props} />