From 144729b14f5fcab3f4fde0c14cde469e072b9ca4 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Tue, 23 Jun 2026 23:38:59 +0000 Subject: [PATCH] refcator: create useFuzzySearch hook --- site/src/hooks/useFuzzySearch.ts | 51 +++++++++++++++++++ .../TemplateBuilder/ModuleSelectStep.tsx | 35 +++---------- 2 files changed, 57 insertions(+), 29 deletions(-) create mode 100644 site/src/hooks/useFuzzySearch.ts diff --git a/site/src/hooks/useFuzzySearch.ts b/site/src/hooks/useFuzzySearch.ts new file mode 100644 index 0000000000..37036b1ba2 --- /dev/null +++ b/site/src/hooks/useFuzzySearch.ts @@ -0,0 +1,51 @@ +import { useMemo } from "react"; +import uFuzzy from "ufuzzy"; + +const fuzzyFinder = new uFuzzy({ + intraMode: 1, + intraIns: 1, + intraSub: 1, + intraTrn: 1, + intraDel: 1, +}); + +type UseFuzzySearchOptions = { + allItems: Readonly>; + searchText: string; + + /** + * The item properties to fuzzy search against + */ + searchProperties: Array; +}; + +export function useFuzzySearch({ + allItems, + searchText, + searchProperties, +}: UseFuzzySearchOptions) { + const query = searchText.trim(); + + const searchedItems = useMemo(() => { + if (!query) { + return allItems; + } + + // Search several string fields by concatenating them together + // https://github.com/leeoniya/uFuzzy/issues/7 + const allItemsAsStrings = allItems.map((item) => + searchProperties.map((p) => item[p]).join("|"), + ); + + const [map, info, sorted] = fuzzyFinder.search(allItemsAsStrings, query); + + // We hit an invalid state somehow + if (!map || !info || !sorted) { + return []; + } + + return sorted.map((i) => allItems[info.idx[i]]); + }, [allItems, query, searchProperties]); + + return searchedItems; +} diff --git a/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx b/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx index c02d60860a..f70d1f8a56 100644 --- a/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx +++ b/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx @@ -1,6 +1,5 @@ import { type FC, type PropsWithChildren, useMemo, useState } from "react"; import { useQuery } from "react-query"; -import uFuzzy from "ufuzzy"; import { templateBuilderModules } from "#/api/queries/templateBuilder"; import type { TemplateBuilderComposeModule, @@ -10,6 +9,7 @@ import { Alert, AlertDescription, AlertTitle } from "#/components/Alert/Alert"; import { ErrorAlert } from "#/components/Alert/ErrorAlert"; import { Loader } from "#/components/Loader/Loader"; import { SearchField } from "#/components/SearchField/SearchField"; +import { useFuzzySearch } from "#/hooks/useFuzzySearch"; import { ModuleCard } from "./ModuleCard"; import { moduleHasConfigurableVars, @@ -52,14 +52,6 @@ const ModuleName: FC = ({ children }) => { ); }; -const fuzzyFinder = new uFuzzy({ - intraMode: 1, - intraIns: 1, - intraSub: 1, - intraTrn: 1, - intraDel: 1, -}); - const ConflictWarning: FC = ({ moduleA, moduleB }) => { return (
@@ -79,27 +71,12 @@ export const ModuleSelectStep: FC = ({ const { data, error, isLoading } = useQuery(templateBuilderModules(baseId)); const [moduleSearchText, setModuleSearchText] = useState(""); const modules = data?.modules ?? []; - const searchText = moduleSearchText.trim(); - const searchedModules = useMemo(() => { - if (!searchText) { - return modules; - } - - const [map, info, sorted] = fuzzyFinder.search( - // Search several string fields by concatenating them together - // https://github.com/leeoniya/uFuzzy/issues/7 - modules.map((module) => `${module.display_name}|${module.description}`), - searchText, - ); - - // We hit an invalid state somehow - if (!map || !info || !sorted) { - return []; - } - - return sorted.map((i) => modules[info.idx[i]]); - }, [modules, searchText]); + const searchedModules = useFuzzySearch({ + allItems: modules, + searchText: moduleSearchText, + searchProperties: ["display_name", "description"], + }); const selectedSet = useMemo( () => new Set(selectedModuleIds),