From 78d2fcbf494a888ced6315d95b9db56b67da4c86 Mon Sep 17 00:00:00 2001 From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:59:01 -0300 Subject: [PATCH] perf(sql-lab): debounce schema browser search (#39489) Co-authored-by: Claude Sonnet 4.6 (cherry picked from commit 9fe3f634ec611713e6eb50d5db72faac2dc3ddcf) --- .../components/TableExploreTree/index.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/superset-frontend/src/SqlLab/components/TableExploreTree/index.tsx b/superset-frontend/src/SqlLab/components/TableExploreTree/index.tsx index f1f27bf768b..af37bff0c2b 100644 --- a/superset-frontend/src/SqlLab/components/TableExploreTree/index.tsx +++ b/superset-frontend/src/SqlLab/components/TableExploreTree/index.tsx @@ -24,6 +24,7 @@ import { type ChangeEvent, useMemo, } from 'react'; +import { useDebounceValue } from 'src/hooks/useDebounceValue'; import { useSelector, useDispatch, shallowEqual } from 'react-redux'; import { styled, css, useTheme } from '@apache-superset/core/theme'; import { t } from '@apache-superset/core/translation'; @@ -313,6 +314,7 @@ const TableExploreTree: React.FC = ({ queryEditorId }) => { }, [sortedTreeData, sortedTables]); const [searchTerm, setSearchTerm] = useState(''); + const debouncedSearchTerm = useDebounceValue(searchTerm); const handleSearchChange = useCallback( ({ target }: ChangeEvent) => setSearchTerm(target.value), [], @@ -370,9 +372,9 @@ const TableExploreTree: React.FC = ({ queryEditorId }) => { // Check if any nodes match the search term const hasMatchingNodes = useMemo(() => { - if (!searchTerm) return true; + if (!debouncedSearchTerm) return true; - const lowerTerm = searchTerm.toLowerCase(); + const lowerTerm = debouncedSearchTerm.toLowerCase(); const checkNode = (node: TreeNodeData): boolean => { if (node.type === 'empty') return false; @@ -384,7 +386,7 @@ const TableExploreTree: React.FC = ({ queryEditorId }) => { }; return displayTreeData.some(node => checkNode(node)); - }, [searchTerm, displayTreeData]); + }, [debouncedSearchTerm, displayTreeData]); // Node renderer for react-arborist const renderNode = useCallback( @@ -393,7 +395,7 @@ const TableExploreTree: React.FC = ({ queryEditorId }) => { {...props} manuallyOpenedNodes={manuallyOpenedNodes} loadingNodes={loadingNodes} - searchTerm={searchTerm} + searchTerm={debouncedSearchTerm} catalog={catalog} pinnedTableKeys={pinnedTableKeys} pinnedSchemas={pinnedSchemas} @@ -423,7 +425,7 @@ const TableExploreTree: React.FC = ({ queryEditorId }) => { toggleSortColumns, loadingNodes, manuallyOpenedNodes, - searchTerm, + debouncedSearchTerm, ], ); @@ -482,7 +484,7 @@ const TableExploreTree: React.FC = ({ queryEditorId }) => { return ; } - if (searchTerm && !hasMatchingNodes) { + if (debouncedSearchTerm && !hasMatchingNodes) { return ( = ({ queryEditorId }) => { height={height || 500} rowHeight={ROW_HEIGHT} indent={16} - searchTerm={searchTerm} + searchTerm={debouncedSearchTerm} searchMatch={searchMatch} disableDrag disableDrop @@ -525,7 +527,7 @@ const TableExploreTree: React.FC = ({ queryEditorId }) => { // react-arborist marks all schemas as open (isOpen=true) even before any // user interaction. Using treeRef in that case would treat every first // click as a close action, so fall back to manuallyOpenedNodes instead. - const wasOpen = searchTerm + const wasOpen = debouncedSearchTerm ? (treeRef.current?.get(id)?.isOpen ?? manuallyOpenedNodes[id] ?? false)