From 91e9ef232dac8b0142a3df4c2ad24d6119340d05 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 17 Jun 2021 11:19:33 +0300 Subject: [PATCH] Memoize SearchResults (#35851) --- .../search/components/SearchResults.tsx | 140 +++++++++--------- .../search/hooks/useManageDashboards.ts | 11 +- public/app/features/search/hooks/useSearch.ts | 25 ++-- .../features/search/hooks/useSearchQuery.ts | 13 +- 4 files changed, 97 insertions(+), 92 deletions(-) diff --git a/public/app/features/search/components/SearchResults.tsx b/public/app/features/search/components/SearchResults.tsx index 5560ff88498..078f56d3ce8 100644 --- a/public/app/features/search/components/SearchResults.tsx +++ b/public/app/features/search/components/SearchResults.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, memo } from 'react'; import { css } from '@emotion/css'; import { FixedSizeList } from 'react-window'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -22,82 +22,78 @@ export interface Props { const { section: sectionLabel, items: itemsLabel } = selectors.components.Search; -export const SearchResults: FC = ({ - editable, - loading, - onTagSelected, - onToggleChecked, - onToggleSection, - results, - layout, -}) => { - const theme = useTheme(); - const styles = getSectionStyles(theme); - const itemProps = { editable, onToggleChecked, onTagSelected }; - const renderFolders = () => { - return ( -
- {results.map((section) => { - return ( -
- - {section.expanded && ( -
- {section.items.map((item) => ( - - ))} -
- )} -
- ); - })} -
- ); - }; - const renderDashboards = () => { - const items = results[0]?.items; - return ( -
- - {({ height }) => ( - - {({ index, style }) => { - const item = items[index]; - // The wrapper div is needed as the inner SearchItem has margin-bottom spacing - // And without this wrapper there is no room for that margin - return ( -
- +export const SearchResults: FC = memo( + ({ editable, loading, onTagSelected, onToggleChecked, onToggleSection, results, layout }) => { + const theme = useTheme(); + const styles = getSectionStyles(theme); + const itemProps = { editable, onToggleChecked, onTagSelected }; + const renderFolders = () => { + return ( +
+ {results.map((section) => { + return ( +
+ + {section.expanded && ( +
+ {section.items.map((item) => ( + + ))}
- ); - }} - - )} - + )} +
+ ); + })} +
+ ); + }; + const renderDashboards = () => { + const items = results[0]?.items; + return ( +
+ + {({ height }) => ( + + {({ index, style }) => { + const item = items[index]; + // The wrapper div is needed as the inner SearchItem has margin-bottom spacing + // And without this wrapper there is no room for that margin + return ( +
+ +
+ ); + }} +
+ )} +
+
+ ); + }; + + if (loading) { + return ; + } else if (!results || !results.length) { + return
No dashboards matching your query were found.
; + } + + return ( +
+ {layout === SearchLayout.Folders ? renderFolders() : renderDashboards()}
); - }; - - if (loading) { - return ; - } else if (!results || !results.length) { - return
No dashboards matching your query were found.
; } +); - return ( -
- {layout === SearchLayout.Folders ? renderFolders() : renderDashboards()} -
- ); -}; +SearchResults.displayName = 'SearchResults'; const getSectionStyles = stylesFactory((theme: GrafanaTheme) => { const { md } = theme.spacing; diff --git a/public/app/features/search/hooks/useManageDashboards.ts b/public/app/features/search/hooks/useManageDashboards.ts index 4418adc74a7..7561e5ec9e0 100644 --- a/public/app/features/search/hooks/useManageDashboards.ts +++ b/public/app/features/search/hooks/useManageDashboards.ts @@ -1,4 +1,4 @@ -import { useMemo, useReducer } from 'react'; +import { useCallback, useMemo, useReducer } from 'react'; import { FolderDTO } from 'app/types'; import { contextSrv } from 'app/core/services/context_srv'; import { DashboardQuery, DashboardSection, OnDeleteItems, OnMoveItems, OnToggleChecked } from '../types'; @@ -23,9 +23,12 @@ export const useManageDashboards = ( dispatch, } = useSearch(query, reducer, {}); - const onToggleChecked: OnToggleChecked = (item) => { - dispatch({ type: TOGGLE_CHECKED, payload: item }); - }; + const onToggleChecked: OnToggleChecked = useCallback( + (item) => { + dispatch({ type: TOGGLE_CHECKED, payload: item }); + }, + [dispatch] + ); const onToggleAllChecked = () => { dispatch({ type: TOGGLE_ALL_CHECKED }); diff --git a/public/app/features/search/hooks/useSearch.ts b/public/app/features/search/hooks/useSearch.ts index df62b9a6bd3..cc138226294 100644 --- a/public/app/features/search/hooks/useSearch.ts +++ b/public/app/features/search/hooks/useSearch.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import { useDebounce } from 'react-use'; import { SearchSrv } from 'app/core/services/search_srv'; import { backendSrv } from 'app/core/services/backend_srv'; @@ -35,17 +35,20 @@ export const useSearch: UseSearch = (query, reducer, params = {}) => { useDebounce(search, 300, [query, queryParsing]); - const onToggleSection = (section: DashboardSection) => { - if (hasId(section.title) && !section.items.length) { - dispatch({ type: FETCH_ITEMS_START, payload: section.id }); - backendSrv.search({ folderIds: [section.id] }).then((items) => { - dispatch({ type: FETCH_ITEMS, payload: { section, items } }); + const onToggleSection = useCallback( + (section: DashboardSection) => { + if (hasId(section.title) && !section.items.length) { + dispatch({ type: FETCH_ITEMS_START, payload: section.id }); + backendSrv.search({ folderIds: [section.id] }).then((items) => { + dispatch({ type: FETCH_ITEMS, payload: { section, items } }); + dispatch({ type: TOGGLE_SECTION, payload: section }); + }); + } else { dispatch({ type: TOGGLE_SECTION, payload: section }); - }); - } else { - dispatch({ type: TOGGLE_SECTION, payload: section }); - } - }; + } + }, + [dispatch] + ); return { state, dispatch, onToggleSection }; }; diff --git a/public/app/features/search/hooks/useSearchQuery.ts b/public/app/features/search/hooks/useSearchQuery.ts index 9817cf6cd71..91862b5093e 100644 --- a/public/app/features/search/hooks/useSearchQuery.ts +++ b/public/app/features/search/hooks/useSearchQuery.ts @@ -1,4 +1,4 @@ -import { FormEvent, useReducer } from 'react'; +import { FormEvent, useCallback, useReducer } from 'react'; import { debounce } from 'lodash'; import { SelectableValue } from '@grafana/data'; import { locationService } from '@grafana/runtime'; @@ -32,10 +32,13 @@ export const useSearchQuery = (defaults: Partial) => { updateLocation({ tag: tags }); }; - const onTagAdd = (tag: string) => { - dispatch({ type: ADD_TAG, payload: tag }); - updateLocation({ tag: [...query.tag, tag] }); - }; + const onTagAdd = useCallback( + (tag: string) => { + dispatch({ type: ADD_TAG, payload: tag }); + updateLocation({ tag: [...query.tag, tag] }); + }, + [query.tag] + ); const onClearFilters = () => { dispatch({ type: CLEAR_FILTERS });