From 6a11b5d6635771bae1f044f4b8cc83193b74ed07 Mon Sep 17 00:00:00 2001 From: Harshith Mohan Date: Tue, 5 Dec 2023 22:35:10 +0530 Subject: [PATCH] Add search to collection (#708) * Add search to collection * Use `useDebounce` wherever applicable * Add 'Infinite' suffix to paginated merged queries --- src/components/Collection/CollectionTitle.tsx | 9 ++- src/components/Collection/CollectionView.tsx | 71 ++++++++++++++++--- .../Unrecognized/AvDumpSeriesSelectModal.tsx | 62 +++++----------- src/core/buildFilter.ts | 14 ++++ src/core/rtkQuery/splitV3Api/collectionApi.ts | 8 +-- src/core/rtkQuery/splitV3Api/filterApi.ts | 60 ++++++++++++++++ src/core/rtkQuery/splitV3Api/seriesApi.ts | 2 +- src/core/rtkQuery/splitV3Api/webuiApi.ts | 4 +- src/core/types/api/filter.ts | 62 ++++++++++++++++ src/pages/collection/Collection.tsx | 33 +++++++-- .../UnrecognizedUtilityTabs/LinkFilesTab.tsx | 35 +++------ 11 files changed, 265 insertions(+), 95 deletions(-) create mode 100644 src/core/buildFilter.ts create mode 100644 src/core/rtkQuery/splitV3Api/filterApi.ts create mode 100644 src/core/types/api/filter.ts diff --git a/src/components/Collection/CollectionTitle.tsx b/src/components/Collection/CollectionTitle.tsx index 0d5a1a743..28fb46fa0 100644 --- a/src/components/Collection/CollectionTitle.tsx +++ b/src/components/Collection/CollectionTitle.tsx @@ -7,9 +7,10 @@ import cx from 'classnames'; type Props = { count: number; filterOrGroup?: string; + searchQuery: string; }; -const CollectionTitle = ({ count, filterOrGroup }: Props) => ( +const CollectionTitle = ({ count, filterOrGroup, searchQuery }: Props) => (
Entire Collection @@ -20,6 +21,12 @@ const CollectionTitle = ({ count, filterOrGroup }: Props) => ( {filterOrGroup} )} + {searchQuery && ( + <> + + Search Results + + )} | {/* Count is set to -1 when series data is empty and is used as a flag to signify that in other places */} diff --git a/src/components/Collection/CollectionView.tsx b/src/components/Collection/CollectionView.tsx index 43b888340..664145e08 100644 --- a/src/components/Collection/CollectionView.tsx +++ b/src/components/Collection/CollectionView.tsx @@ -10,12 +10,15 @@ import { debounce } from 'lodash'; import ListViewItem from '@/components/Collection/ListViewItem'; import PosterViewItem from '@/components/Collection/PosterViewItem'; -import { useLazyGetGroupSeriesQuery, useLazyGetGroupsQuery } from '@/core/rtkQuery/splitV3Api/collectionApi'; +import buildFilter from '@/core/buildFilter'; +import { useLazyGetGroupSeriesQuery } from '@/core/rtkQuery/splitV3Api/collectionApi'; +import { useGetFilterQuery, useLazyGetFilteredGroupsInfiniteQuery } from '@/core/rtkQuery/splitV3Api/filterApi'; import { useGetSettingsQuery } from '@/core/rtkQuery/splitV3Api/settingsApi'; -import { useLazyGetGroupViewQuery } from '@/core/rtkQuery/splitV3Api/webuiApi'; +import { useLazyGetGroupViewInfiniteQuery } from '@/core/rtkQuery/splitV3Api/webuiApi'; import { initialSettings } from '@/pages/settings/SettingsPage'; import type { InfiniteResultType } from '@/core/types/api'; +import type { FilterCondition, FilterType } from '@/core/types/api/filter'; import type { SeriesType } from '@/core/types/api/series'; type Props = { @@ -23,6 +26,7 @@ type Props = { setGroupTotal: (total: number) => void; setTimelineSeries: (series: SeriesType[]) => void; isSidebarOpen: boolean; + searchQuery: string; }; const defaultPageSize = 50; @@ -40,10 +44,40 @@ export const listItemSize = { gap: 32, }; -const CollectionView = ({ isSidebarOpen, mode, setGroupTotal, setTimelineSeries }: Props) => { +const getFilter = (query: string, filterCondition?: FilterCondition): FilterType => { + let finalCondition: FilterCondition | undefined; + if (query) { + const searchCondition: FilterCondition = { + Type: 'StringFuzzyMatches', + Left: { + Type: 'NameSelector', + }, + Parameter: query, + }; + + if (filterCondition) { + finalCondition = buildFilter([searchCondition, filterCondition]); + } else { + finalCondition = buildFilter([searchCondition]); + } + } else if (filterCondition) { + finalCondition = buildFilter([filterCondition]); + } + + return ( + finalCondition + ? { + Expression: finalCondition, + } + : {} + ); +}; + +const CollectionView = ({ isSidebarOpen, mode, searchQuery, setGroupTotal, setTimelineSeries }: Props) => { const { filterId, groupId } = useParams(); const [currentFilterId, setCurrentFilterId] = useState(filterId); + const [currentSearch, setCurrentSearch] = useState(searchQuery); const settingsQuery = useGetSettingsQuery(); const settings = useMemo(() => settingsQuery?.data ?? initialSettings, [settingsQuery]); @@ -54,6 +88,8 @@ const CollectionView = ({ isSidebarOpen, mode, setGroupTotal, setTimelineSeries [mode, showRandomPosterGrid, showRandomPosterList], ); + const filterQuery = useGetFilterQuery({ filterId: filterId! }, { skip: !filterId }); + const [itemWidth, itemHeight, itemGap] = useMemo(() => { if (mode === 'poster') return [posterItemSize.width, posterItemSize.height, posterItemSize.gap]; return [ @@ -65,7 +101,7 @@ const CollectionView = ({ isSidebarOpen, mode, setGroupTotal, setTimelineSeries const [fetchingPage, setFetchingPage] = useState(false); - const [fetchGroups, groupsData] = useLazyGetGroupsQuery(); + const [fetchGroups, groupsData] = useLazyGetFilteredGroupsInfiniteQuery(); const [fetchSeries, seriesDataResult] = useLazyGetGroupSeriesQuery(); const [seriesData, setSeriesData] = useState>({ pages: [], total: -1 }); // This is to set an extra arg for groupsQuery so that cache is invalidated correctly. Using state because this should not change once component is mounted. @@ -79,7 +115,7 @@ const CollectionView = ({ isSidebarOpen, mode, setGroupTotal, setTimelineSeries [groupId, groupsData, seriesData], ); - const [fetchGroupExtras, groupExtrasData] = useLazyGetGroupViewQuery(); + const [fetchGroupExtras, groupExtrasData] = useLazyGetGroupViewInfiniteQuery(); const groupExtras = groupExtrasData.data ?? []; useEffect(() => { @@ -100,11 +136,13 @@ const CollectionView = ({ isSidebarOpen, mode, setGroupTotal, setTimelineSeries fetchGroups({ page, pageSize, - filterId: filterId ?? '0', randomImages: showRandomPoster, + filterCriteria: getFilter(searchQuery, filterId ? filterQuery.data?.Expression : undefined), queryId: groupQueryId, }).then( (result) => { + setCurrentFilterId(filterId); + if (!result.data) return; const ids = result.data.pages[page].map(group => group.IDs.ID); @@ -120,7 +158,18 @@ const CollectionView = ({ isSidebarOpen, mode, setGroupTotal, setTimelineSeries .then(result => result.data && setSeriesData(result.data)) .catch(error => console.error(error)).finally(() => setFetchingPage(false)); } - }, 200), [groupId, fetchGroups, pageSize, filterId, showRandomPoster, groupQueryId, fetchGroupExtras, fetchSeries]); + }, 200), [ + groupId, + fetchGroups, + pageSize, + showRandomPoster, + searchQuery, + filterId, + filterQuery.data?.Expression, + groupQueryId, + fetchGroupExtras, + fetchSeries, + ]); useEffect(() => { fetchPage.cancel(); @@ -129,8 +178,10 @@ const CollectionView = ({ isSidebarOpen, mode, setGroupTotal, setTimelineSeries let shouldFetch: boolean; if (groupId) { shouldFetch = true; + } else if (searchQuery !== currentSearch) { + setCurrentSearch(searchQuery); + shouldFetch = true; } else if (filterId !== currentFilterId) { - setCurrentFilterId(filterId); shouldFetch = true; } else { shouldFetch = groupsData.isUninitialized; @@ -143,7 +194,7 @@ const CollectionView = ({ isSidebarOpen, mode, setGroupTotal, setTimelineSeries return () => fetchPage.cancel(); // TODO: Figure out how to do it better // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filterId, groupId, groupsData.isUninitialized, fetchPage]); + }, [filterId, groupId, groupsData.isUninitialized, fetchPage, searchQuery]); useEffect(() => { if (!groupId) setSeriesData({ pages: [], total: -1 }); @@ -181,7 +232,7 @@ const CollectionView = ({ isSidebarOpen, mode, setGroupTotal, setTimelineSeries {/* This is always equal width to the actual grid container so we are using the ref here */} {/* Otherwise we would need two refs to remove flicker */}
- {isLoading || seriesData.total === -1 + {isLoading || (groupId && seriesData.total === -1) ? : 'No series/groups available!'}
diff --git a/src/components/Utilities/Unrecognized/AvDumpSeriesSelectModal.tsx b/src/components/Utilities/Unrecognized/AvDumpSeriesSelectModal.tsx index bb648785f..9551a8186 100644 --- a/src/components/Utilities/Unrecognized/AvDumpSeriesSelectModal.tsx +++ b/src/components/Utilities/Unrecognized/AvDumpSeriesSelectModal.tsx @@ -1,15 +1,15 @@ -import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { mdiInformationOutline, mdiLoading, mdiMagnify, mdiOpenInNew } from '@mdi/js'; import { Icon } from '@mdi/react'; -import { debounce, forEach } from 'lodash'; -import { useEventCallback } from 'usehooks-ts'; +import { forEach } from 'lodash'; +import { useDebounce, useEventCallback } from 'usehooks-ts'; import Button from '@/components/Input/Button'; import Input from '@/components/Input/Input'; import ModalPanel from '@/components/Panels/ModalPanel'; import toast from '@/components/Toast'; import { usePostFileRescanMutation } from '@/core/rtkQuery/splitV3Api/fileApi'; -import { useLazyGetSeriesAniDBSearchQuery } from '@/core/rtkQuery/splitV3Api/seriesApi'; +import { useGetSeriesAniDBSearchQuery } from '@/core/rtkQuery/splitV3Api/seriesApi'; import { copyToClipboard } from '@/core/util'; import { detectShow, findMostCommonShowName } from '@/core/utilities/auto-match-logic'; @@ -62,24 +62,19 @@ function AvDumpSeriesSelectModal({ getLinks, onClose, show }: Props) { }); return { ed2kLinks: tempEd2kLinks, links: tempLinks, fileIds: tempFileIds }; }, [getLinks, show]); - const commonSeries = useMemo(() => findMostCommonShowName(links.map(link => detectShow(link.split('|')[2]))), [ - links, - ]); - const initRef = useRef(false); - const [searchText, setSearchText] = useState(() => commonSeries); - const [searchTrigger, searchResults] = useLazyGetSeriesAniDBSearchQuery(); + const commonSeries = useMemo( + () => findMostCommonShowName(links.map(link => detectShow(link.split('|')[2]))), + [links], + ); + const [searchText, setSearchText] = useState(''); const [activeStep, setActiveStep] = useState(1); const [copyFailed, setCopyFailed] = useState(false); + const debouncedSearch = useDebounce(searchText, 200); + const searchQuery = useGetSeriesAniDBSearchQuery({ query: debouncedSearch }, { skip: !debouncedSearch }); - const debouncedSearch = useRef( - debounce((query: string) => { - searchTrigger({ query, pageSize: 20 }).catch(() => {}); - }, 200), - ).current; - - const handleClose = () => { - onClose(false); - }; + useEffect(() => { + setSearchText(commonSeries); + }, [commonSeries]); const handleNextStep = () => { setActiveStep(activeStep + 1); @@ -90,18 +85,6 @@ function AvDumpSeriesSelectModal({ getLinks, onClose, show }: Props) { setActiveStep(activeStep - 1); }; - const handleSearch = useEventCallback((query: string) => { - setSearchText(query); - if (query !== '') { - if (initRef.current) { - initRef.current = false; - searchTrigger({ query, pageSize: 20 }).catch(() => {}); - } else { - debouncedSearch(query); - } - } - }); - const handleCopy = () => { copyToClipboard(ed2kLinks) .then(() => { @@ -129,17 +112,8 @@ function AvDumpSeriesSelectModal({ getLinks, onClose, show }: Props) { if (failedFiles !== fileIds.length) toast.success(`Rescanning ${fileIds.length} files!`); }); - useEffect(() => () => { - debouncedSearch.cancel(); - }, [debouncedSearch]); - - useEffect(() => { - handleSearch(commonSeries); - }, [commonSeries, handleSearch]); - useLayoutEffect(() => () => { if (show) return; - initRef.current = true; setSearchText(''); setClickedLink(false); setCopyFailed(false); @@ -178,7 +152,7 @@ function AvDumpSeriesSelectModal({ getLinks, onClose, show }: Props) { @@ -216,18 +190,18 @@ function AvDumpSeriesSelectModal({ getLinks, onClose, show }: Props) { value={searchText} type="text" placeholder="Search..." - onChange={e => handleSearch(e.target.value)} + onChange={e => setSearchText(e.target.value)} startIcon={mdiMagnify} />
- {initRef.current || searchResults.isError || searchResults.isFetching + {searchQuery.isError || searchQuery.isFetching ? (
) - : (searchResults.data ?? []).map(result => ( + : (searchQuery.data ?? []).map(result => ( { + if (filters.length > 1) { + return { + Type: 'And', + Left: filters[0], + Right: buildFilter(filters.slice(1)), + }; + } + return filters[0]; +}; + +export default buildFilter; diff --git a/src/core/rtkQuery/splitV3Api/collectionApi.ts b/src/core/rtkQuery/splitV3Api/collectionApi.ts index 87b76a620..5baedef18 100644 --- a/src/core/rtkQuery/splitV3Api/collectionApi.ts +++ b/src/core/rtkQuery/splitV3Api/collectionApi.ts @@ -9,7 +9,7 @@ import type { SeriesType } from '@/core/types/api/series'; const collectionApi = splitV3Api.injectEndpoints({ endpoints: build => ({ - getGroups: build.query< + getGroupsInfinite: build.query< InfiniteResultType, PaginationType & { filterId: string, randomImages?: boolean, queryId: number } >({ @@ -81,17 +81,13 @@ const collectionApi = splitV3Api.injectEndpoints({ params: { includeEmpty, topLevelOnly }, }), }), - getFilter: build.query({ - query: ({ filterId }) => ({ url: `Filter/${filterId}` }), - }), }), }); export const { - useGetFilterQuery, useGetGroupQuery, useLazyGetFiltersQuery, useLazyGetGroupSeriesQuery, - useLazyGetGroupsQuery, + useLazyGetGroupsInfiniteQuery, useLazyGetTopFiltersQuery, } = collectionApi; diff --git a/src/core/rtkQuery/splitV3Api/filterApi.ts b/src/core/rtkQuery/splitV3Api/filterApi.ts new file mode 100644 index 000000000..0520b7ba6 --- /dev/null +++ b/src/core/rtkQuery/splitV3Api/filterApi.ts @@ -0,0 +1,60 @@ +import { defaultSerializeQueryArgs } from '@reduxjs/toolkit/query'; +import { omit } from 'lodash'; + +import { splitV3Api } from '@/core/rtkQuery/splitV3Api'; + +import type { InfiniteResultType, ListResultType, PaginationType } from '@/core/types/api'; +import type { CollectionGroupType } from '@/core/types/api/collection'; +import type { FilterType } from '@/core/types/api/filter'; + +const filterApi = splitV3Api.injectEndpoints({ + endpoints: build => ({ + getFilter: build.query({ + query: ({ filterId }) => ({ + url: `Filter/${filterId}`, + params: { + withConditions: true, + }, + }), + }), + getFilteredGroupsInfinite: build.query< + InfiniteResultType, + PaginationType & { randomImages?: boolean, filterCriteria: FilterType, queryId: number } + >({ + query: ({ filterCriteria, queryId: _, ...params }) => ({ + url: 'Filter/Preview/Group', + method: 'POST', + params, + body: filterCriteria, + }), + transformResponse: (response: ListResultType, _, args) => ({ + pages: { + [args.page ?? 1]: response.List, + }, + total: response.Total, + }), + // Only have one cache entry because the arg always maps to one string + serializeQueryArgs: ({ endpointDefinition, endpointName, queryArgs }) => + defaultSerializeQueryArgs({ + endpointName, + queryArgs: omit(queryArgs, ['page']), + endpointDefinition, + }), + // Always merge incoming data to the cache entry + merge: (currentCache, newItems) => { + const tempCache = { ...currentCache }; + tempCache.pages = { ...currentCache.pages, ...newItems.pages }; + return tempCache; + }, + // Refetch when the page arg changes + forceRefetch({ currentArg, previousArg }) { + return currentArg !== previousArg; + }, + }), + }), +}); + +export const { + useGetFilterQuery, + useLazyGetFilteredGroupsInfiniteQuery, +} = filterApi; diff --git a/src/core/rtkQuery/splitV3Api/seriesApi.ts b/src/core/rtkQuery/splitV3Api/seriesApi.ts index b4874daca..60f39fd73 100644 --- a/src/core/rtkQuery/splitV3Api/seriesApi.ts +++ b/src/core/rtkQuery/splitV3Api/seriesApi.ts @@ -273,6 +273,7 @@ export const { useGetAniDBRelatedQuery, useGetAniDBSimilarQuery, useGetSeriesAniDBEpisodesQuery, + useGetSeriesAniDBSearchQuery, useGetSeriesCastQuery, useGetSeriesImagesQuery, useGetSeriesQuery, @@ -280,7 +281,6 @@ export const { useGetSeriesWithManuallyLinkedFilesQuery, useGetSeriesWithoutFilesQuery, useLazyGetSeriesAniDBQuery, - useLazyGetSeriesAniDBSearchQuery, useLazyGetSeriesEpisodesInfiniteQuery, useLazyGetSeriesEpisodesQuery, useLazyGetSeriesFilesQuery, diff --git a/src/core/rtkQuery/splitV3Api/webuiApi.ts b/src/core/rtkQuery/splitV3Api/webuiApi.ts index 8e3dcdeca..7d977d9ba 100644 --- a/src/core/rtkQuery/splitV3Api/webuiApi.ts +++ b/src/core/rtkQuery/splitV3Api/webuiApi.ts @@ -26,7 +26,7 @@ export type SeriesFileSummaryApiRequest = { const webuiApi = splitV3Api.injectEndpoints({ endpoints: build => ({ - getGroupView: build.query({ + getGroupViewInfinite: build.query({ query: params => ({ url: 'WebUI/GroupView', body: params, method: 'POST' }), // Only have one cache entry because the arg always maps to one string serializeQueryArgs: ({ endpointName }) => endpointName, @@ -75,7 +75,7 @@ export const { useGetSeriesOverviewQuery, useGetWebuiThemesQuery, useGetWebuiUpdateCheckQuery, - useLazyGetGroupViewQuery, + useLazyGetGroupViewInfiniteQuery, useLazyGetSeriesFileSummeryQuery, useLazyGetWebuiUpdateCheckQuery, usePostWebuiUpdateMutation, diff --git a/src/core/types/api/filter.ts b/src/core/types/api/filter.ts new file mode 100644 index 000000000..99ca55803 --- /dev/null +++ b/src/core/types/api/filter.ts @@ -0,0 +1,62 @@ +type ExpressionType = + | 'And' + | 'Not' + | 'Or' + | 'HasUnwatchedEpisodes' + | 'HasWatchedEpisodes' + | 'NameSelector' + | 'StringContains' + | 'StringEndsWith' + | 'StringEquals' + | 'StringFuzzyMatches' + | 'StringNotEquals' + | 'StringRegexMatches' + | 'StringStartsWith'; + +type SortingType = + | 'AddedDate' + | 'AirDate' + | 'AudioLanguageCount' + | 'AverageAniDBRating' + | 'EpisodeCount' + | 'HighestAniDBRating' + | 'HighestUserRating' + | 'LastAddedDate' + | 'LastAirDate' + | 'LastWatchedDate' + | 'LowestAniDBRating' + | 'LowestUserRating' + | 'MissingEpisodeCollectingCount' + | 'MissingEpisodeCount' + | 'Name' + | 'SeriesCount' + | 'SortingName' + | 'SubtitleLanguageCount' + | 'TotalEpisodeCount' + | 'UnwatchedEpisodeCount' + | 'WatchedDate' + | 'WatchedEpisodeCount'; + +export type FilterCondition = { + Type: ExpressionType; + Left?: FilterCondition; + Right?: FilterCondition; + Parameter?: string; + SecondParameter?: string; +}; + +type SortingCriteria = { + Type: SortingType; + Next?: SortingCriteria; + IsInverted: boolean; +}; + +export type FilterType = { + Name?: string; + ParentID?: number; + IsDirectory?: boolean; + IsHidden?: boolean; + ApplyAtSeriesLevel?: boolean; + Expression?: FilterCondition; + SortingCriteria?: SortingCriteria; +}; diff --git a/src/pages/collection/Collection.tsx b/src/pages/collection/Collection.tsx index df135772c..a9989eee6 100644 --- a/src/pages/collection/Collection.tsx +++ b/src/pages/collection/Collection.tsx @@ -1,17 +1,27 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router'; import { Link } from 'react-router-dom'; -import { mdiCogOutline, mdiFilterMenuOutline, mdiFilterOutline, mdiFormatListText, mdiViewGridOutline } from '@mdi/js'; +import { + mdiCogOutline, + mdiFilterMenuOutline, + mdiFilterOutline, + mdiFormatListText, + mdiMagnify, + mdiViewGridOutline, +} from '@mdi/js'; import { Icon } from '@mdi/react'; import cx from 'classnames'; import { cloneDeep } from 'lodash'; +import { useDebounce } from 'usehooks-ts'; import BackgroundImagePlaceholderDiv from '@/components/BackgroundImagePlaceholderDiv'; import CollectionTitle from '@/components/Collection/CollectionTitle'; import CollectionView from '@/components/Collection/CollectionView'; import DisplaySettingsModal from '@/components/Collection/DisplaySettingsModal'; import FiltersModal from '@/components/Dialogs/FiltersModal'; -import { useGetFilterQuery, useGetGroupQuery } from '@/core/rtkQuery/splitV3Api/collectionApi'; +import Input from '@/components/Input/Input'; +import { useGetGroupQuery } from '@/core/rtkQuery/splitV3Api/collectionApi'; +import { useGetFilterQuery } from '@/core/rtkQuery/splitV3Api/filterApi'; import { useGetSettingsQuery, usePatchSettingsMutation } from '@/core/rtkQuery/splitV3Api/settingsApi'; import { SeriesTypeEnum } from '@/core/types/api/series'; import { dayjs } from '@/core/util'; @@ -69,7 +79,7 @@ const TimelineSidebar = ({ series }: { series: SeriesType[] }) => ( function Collection() { const { filterId, groupId } = useParams(); - const filterData = useGetFilterQuery({ filterId }, { skip: !filterId }); + const filterData = useGetFilterQuery({ filterId: filterId! }, { skip: !filterId }); const groupData = useGetGroupQuery({ groupId: groupId! }, { skip: !groupId }); const subsectionName = groupId ? groupData?.data?.Name : filterId && filterData?.data?.Name; @@ -84,6 +94,8 @@ function Collection() { const [showDisplaySettingsModal, setShowDisplaySettingsModal] = useState(false); const [groupTotal, setGroupTotal] = useState(0); const [timelineSeries, setTimelineSeries] = useState([]); + const [search, setSearch] = useState(''); + const debouncedSearch = useDebounce(search, 200); useEffect(() => { setMode(viewSetting); @@ -106,8 +118,20 @@ function Collection() { <>
- +
+ setSearch(e.target.value)} + /> {!groupId && ( <> setShowFilterModal(true)} icon={mdiFilterMenuOutline} /> @@ -124,6 +148,7 @@ function Collection() { setGroupTotal={setGroupTotal} setTimelineSeries={setTimelineSeries} isSidebarOpen={showFilterSidebar} + searchQuery={debouncedSearch} />
{ - const [searchTrigger, searchResults] = useLazyGetSeriesAniDBSearchQuery(); const [searchText, setSearchText] = useState(placeholder); - - const debouncedSearch = useMemo(() => - debounce((query: string) => { - searchTrigger({ query, pageSize: 40 }).catch(() => {}); - }, 200), [searchTrigger]); + const debouncedSearch = useDebounce(searchText, 200); + const searchQuery = useGetSeriesAniDBSearchQuery({ query: debouncedSearch }, { skip: !debouncedSearch }); const searchRows = useMemo(() => { const rows: React.ReactNode[] = []; if (!seriesUpdating) { - forEach(searchResults.data, (data) => { + forEach(searchQuery.data, (data) => { rows.push(); }); } else { @@ -172,22 +168,7 @@ const AnimeSelectPanel = ( ); } return rows; - }, [searchResults.data, seriesUpdating, updateSelectedSeries]); - - const handleSearch = useEventCallback((e: React.ChangeEvent) => { - const query = e.target.value; - setSearchText(query); - if (query !== '') debouncedSearch(query); - }); - - useEffect(() => { - setSearchText(placeholder); - if (placeholder !== '') debouncedSearch(placeholder); - }, [placeholder, debouncedSearch]); - - useEffect(() => () => { - debouncedSearch.cancel(); - }, [debouncedSearch]); + }, [searchQuery, seriesUpdating, updateSelectedSeries]); return (
@@ -195,7 +176,7 @@ const AnimeSelectPanel = ( id="link-search" type="text" value={searchText} - onChange={handleSearch} + onChange={e => setSearchText(e.target.value)} placeholder="Enter Series Name or AniDB ID..." inputClassName="!p-4" startIcon={mdiMagnify}