diff --git a/src/common/hooks/useFetchSearchData.ts b/src/common/hooks/useFetchSearchData.ts new file mode 100644 index 00000000..2cfba689 --- /dev/null +++ b/src/common/hooks/useFetchSearchData.ts @@ -0,0 +1,199 @@ +import { useCallback } from 'react'; +import { useSetRecoilState, useRecoilState, SetterOrUpdater } from 'recoil'; +import { getByIdentifier } from '@common/api/search.api'; +import { SearchIdentifiers, SearchSegment } from '@common/constants/search.constants'; +import { SearchableIndexQuerySelector } from '@common/constants/complexLookup.constants'; +import { StatusType } from '@common/constants/status.constants'; +import { normalizeQuery } from '@common/helpers/search.helper'; +import { normalizeLccn } from '@common/helpers/validations.helper'; +import { UserNotificationFactory } from '@common/services/userNotification'; +import state from '@state'; +import { useSearchContext } from './useSearchContext'; + +export const useFetchSearhData = () => { + const { + endpointUrl, + searchFilter, + isSortedResults, + navigationSegment, + endpointUrlsBySegments, + searchResultsLimit, + fetchSearchResults, + searchResultsContainer, + searchableIndicesMap, + buildSearchQuery, + precedingRecordsCount, + } = useSearchContext(); + const setIsLoading = useSetRecoilState(state.loadingState.isLoading); + const setMessage = useSetRecoilState(state.search.message); + const [data, setData] = useRecoilState(state.search.data); + const setPageMetadata = useSetRecoilState(state.search.pageMetadata); + const setStatusMessages = useSetRecoilState(state.status.commonMessages); + + const validateAndNormalizeQuery = useCallback( + (type: SearchIdentifiers, query: string) => { + if (type === SearchIdentifiers.LCCN) { + const normalized = normalizeLccn(query); + + !normalized && setMessage('ld.searchInvalidLccn'); + + return normalized; + } + + return normalizeQuery(query); + }, + [setMessage], + ); + + const getEndpointUrl = ({ + selectedSegment, + endpointUrlsBySegments, + endpointUrl, + }: { + selectedSegment?: string; + endpointUrlsBySegments?: EndpointUrlsBySegments; + endpointUrl: string; + }) => { + return selectedSegment ? endpointUrlsBySegments?.[selectedSegment] : endpointUrl; + }; + + const generateQuery = ({ + searchBy, + query, + selectedSegment, + searchableIndicesMap, + baseQuerySelector, + }: { + searchBy: SearchableIndexType; + query: string; + selectedSegment?: string; + searchableIndicesMap?: SearchableIndicesMap; + baseQuerySelector?: SearchableIndexQuerySelector; + }) => { + const selectedSearchableIndices = selectedSegment + ? searchableIndicesMap?.[selectedSegment as SearchSegmentValue] + : searchableIndicesMap; + + return ( + buildSearchQuery?.({ + map: selectedSearchableIndices as SearchableIndexEntries, + selector: baseQuerySelector, + searchBy, + value: query, + }) ?? query + ); + }; + + const fetchDataFromApi = async ({ + endpointUrl, + searchFilter, + isSortedResults, + searchBy, + query, + offset, + limit, + precedingRecordsCount, + resultsContainer, + }: { + endpointUrl: string; + searchFilter?: string; + isSortedResults?: boolean; + searchBy: SearchIdentifiers; + query: string; + offset?: string; + limit?: string; + precedingRecordsCount?: number; + resultsContainer: any; + }) => { + return fetchSearchResults + ? await fetchSearchResults({ + endpointUrl, + searchFilter, + isSortedResults, + searchBy, + query, + offset, + limit, + precedingRecordsCount, + resultsContainer, + }) + : await getByIdentifier({ + endpointUrl, + searchFilter, + isSortedResults, + searchBy, + query, + offset, + limit, + }); + }; + + const handleFetchError = (setStatusMessages: SetterOrUpdater) => { + setStatusMessages(currentStatus => [ + ...currentStatus, + UserNotificationFactory.createMessage(StatusType.error, 'ld.errorFetching'), + ]); + }; + + const fetchData = useCallback( + async ({ + query, + searchBy, + offset, + selectedSegment, + baseQuerySelector = SearchableIndexQuerySelector.Query, + }: FetchDataParams) => { + setMessage(''); + const selectedNavigationSegment = selectedSegment ?? navigationSegment?.value; + + data && setData(null); + + const updatedQuery = validateAndNormalizeQuery(searchBy, query); + if (!updatedQuery) return; + + setIsLoading(true); + + try { + const currentEndpointUrl = getEndpointUrl({ + selectedSegment: selectedNavigationSegment, + endpointUrlsBySegments, + endpointUrl, + }); + const generatedQuery = generateQuery({ + searchBy, + query: updatedQuery, + selectedSegment: selectedNavigationSegment, + searchableIndicesMap, + baseQuerySelector, + }); + const isBrowseSearch = selectedNavigationSegment === SearchSegment.Browse; + + const result = await fetchDataFromApi({ + endpointUrl: currentEndpointUrl ?? '', + searchFilter, + isSortedResults, + searchBy, + query: generatedQuery, + offset: offset?.toString(), + limit: searchResultsLimit?.toString(), + precedingRecordsCount: isBrowseSearch ? precedingRecordsCount : undefined, + resultsContainer: searchResultsContainer?.[selectedNavigationSegment as SearchSegmentValue], + }); + + const { content, totalPages, totalRecords, prev, next } = result; + + if (!content.length) return setMessage('ld.searchNoRdsMatch'); + + setData(content); + setPageMetadata({ totalPages, totalElements: totalRecords, prev, next }); + } catch { + handleFetchError(setStatusMessages); + } finally { + setIsLoading(false); + } + }, + [data, endpointUrl, fetchSearchResults, navigationSegment?.value, searchFilter, isSortedResults], + ); + + return { fetchData }; +}; diff --git a/src/common/hooks/useLoadSearchResults.ts b/src/common/hooks/useLoadSearchResults.ts index 947f2664..eefaf5b1 100644 --- a/src/common/hooks/useLoadSearchResults.ts +++ b/src/common/hooks/useLoadSearchResults.ts @@ -6,7 +6,9 @@ import { SEARCH_RESULTS_LIMIT, SearchIdentifiers } from '@common/constants/searc import { useSearchContext } from './useSearchContext'; import state from '@state'; -export const useLoadSearchResults = (fetchData: ({ query, searchBy, offset }: FetchDataParams) => Promise) => { +export const useLoadSearchResults = ( + fetchData: ({ query, searchBy, offset, selectedSegment, baseQuerySelector }: FetchDataParams) => Promise, +) => { const { hasSearchParams, defaultSearchBy, defaultQuery, getSearchSourceData, getSearchFacetsData } = useSearchContext(); const setData = useSetRecoilState(state.search.data); diff --git a/src/common/hooks/useSearch.ts b/src/common/hooks/useSearch.ts index a3d7e23b..93f43cda 100644 --- a/src/common/hooks/useSearch.ts +++ b/src/common/hooks/useSearch.ts @@ -1,38 +1,24 @@ import { useCallback, useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useSetRecoilState, useRecoilState, useResetRecoilState } from 'recoil'; -import { getByIdentifier } from '@common/api/search.api'; import { SearchableIndexQuerySelector } from '@common/constants/complexLookup.constants'; import { DEFAULT_PAGES_METADATA } from '@common/constants/api.constants'; import { SearchIdentifiers, SearchSegment } from '@common/constants/search.constants'; -import { StatusType } from '@common/constants/status.constants'; -import { generateSearchParamsState, normalizeQuery } from '@common/helpers/search.helper'; -import { normalizeLccn } from '@common/helpers/validations.helper'; -import { UserNotificationFactory } from '@common/services/userNotification'; +import { generateSearchParamsState } from '@common/helpers/search.helper'; import { usePagination } from '@common/hooks/usePagination'; import state from '@state'; import { useSearchContext } from './useSearchContext'; +import { useFetchSearhData } from './useFetchSearchData'; export const useSearch = () => { const { - endpointUrl, - searchFilter, - isSortedResults, hasSearchParams, defaultSearchBy, defaultQuery, navigationSegment, - isVisibleSegments, hasCustomPagination, - endpointUrlsBySegments, - searchResultsLimit, - fetchSearchResults, - searchResultsContainer, searchByControlOptions, - searchableIndicesMap, getSearchSourceData, - buildSearchQuery, - precedingRecordsCount, } = useSearchContext(); const setIsLoading = useSetRecoilState(state.loadingState.isLoading); const [searchBy, setSearchBy] = useRecoilState(state.search.index); @@ -41,12 +27,12 @@ export const useSearch = () => { const [message, setMessage] = useRecoilState(state.search.message); const [data, setData] = useRecoilState(state.search.data); const [pageMetadata, setPageMetadata] = useRecoilState(state.search.pageMetadata); - const setStatusMessages = useSetRecoilState(state.status.commonMessages); const setForceRefreshSearch = useSetRecoilState(state.search.forceRefresh); const resetPreviewContent = useResetRecoilState(state.inputs.previewContent); const [facetsBySegments, setFacetsBySegments] = useRecoilState(state.search.facetsBySegments); const clearFacetsBySegments = useResetRecoilState(state.search.facetsBySegments); + const { fetchData } = useFetchSearhData(); const { getCurrentPageNumber, setCurrentPageNumber, @@ -76,101 +62,6 @@ export const useSearch = () => { setCurrentPageNumber(0); }, []); - const validateAndNormalizeQuery = useCallback( - (type: SearchIdentifiers, query: string) => { - if (type === SearchIdentifiers.LCCN) { - const normalized = normalizeLccn(query); - - !normalized && setMessage('ld.searchInvalidLccn'); - - return normalized; - } - - return normalizeQuery(query); - }, - [setMessage], - ); - - // TODO: refactor this function - const fetchData = useCallback( - async ({ - query, - searchBy, - offset, - selectedSegment, - baseQuerySelector = SearchableIndexQuerySelector.Query, - }: FetchDataParams) => { - setMessage(''); - const selectedNavigationSegment = selectedSegment ?? navigationSegment?.value; - - data && setData(null); - - const updatedQuery = validateAndNormalizeQuery(searchBy, query); - - if (!updatedQuery) return; - - setIsLoading(true); - - try { - const currentEndpointUrl = selectedNavigationSegment - ? endpointUrlsBySegments?.[selectedNavigationSegment] - : endpointUrl; - const selectedSearchableIndices = - isVisibleSegments && selectedNavigationSegment - ? searchableIndicesMap?.[selectedNavigationSegment as SearchSegmentValue] - : searchableIndicesMap; - const generatedQuery = - fetchSearchResults && buildSearchQuery - ? (buildSearchQuery({ - map: selectedSearchableIndices as SearchableIndexEntries, - selector: baseQuerySelector, - searchBy: searchBy as unknown as SearchableIndexType, - value: updatedQuery, - }) as string) - : (updatedQuery as string); - const isBrowseSearch = selectedNavigationSegment === SearchSegment.Browse; - - const result = fetchSearchResults - ? await fetchSearchResults({ - endpointUrl: currentEndpointUrl ?? endpointUrl, - searchFilter, - isSortedResults, - searchBy, - query: generatedQuery, - offset: offset?.toString(), - limit: searchResultsLimit?.toString(), - precedingRecordsCount: isBrowseSearch ? precedingRecordsCount : undefined, - resultsContainer: searchResultsContainer?.[selectedNavigationSegment as SearchSegmentValue], - }) - : await getByIdentifier({ - endpointUrl: currentEndpointUrl ?? endpointUrl, - searchFilter, - isSortedResults, - searchBy, - query: updatedQuery as string, - offset: offset?.toString(), - limit: searchResultsLimit?.toString(), - }); - - const { content, totalPages, totalRecords, prev, next } = result; - - // TODO: pass the message though the context - if (!content.length) return setMessage('ld.searchNoRdsMatch'); - - setData(content); - setPageMetadata({ totalPages, totalElements: totalRecords, prev, next }); - } catch { - setStatusMessages(currentStatus => [ - ...currentStatus, - UserNotificationFactory.createMessage(StatusType.error, 'ld.errorFetching'), - ]); - } finally { - setIsLoading(false); - } - }, - [data, endpointUrl, fetchSearchResults, navigationSegment?.value, searchFilter, isSortedResults], - ); - const submitSearch = useCallback(() => { clearPagination(); resetPreviewContent(); diff --git a/src/types/search.d.ts b/src/types/search.d.ts index b4962cd8..f584724d 100644 --- a/src/types/search.d.ts +++ b/src/types/search.d.ts @@ -13,11 +13,13 @@ type NavigationSegment = { set: Dispatch> | VoidFunction; }; +type EndpointUrlsBySegments = { + [key in SearchSegment]: string; +}; + type SearchParams = { endpointUrl: string; - endpointUrlsBySegments?: { - [key in SearchSegment]: string; - }; + endpointUrlsBySegments?: EndpointUrlsBySegments; primarySegments?: PrimarySegmentsConfig; searchFilter?: string; hasSearchParams: boolean;