Skip to content

Commit

Permalink
code refactoring: fetch search data
Browse files Browse the repository at this point in the history
  • Loading branch information
SKarolFolio committed Oct 24, 2024
1 parent a67200d commit fdb1b3d
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 116 deletions.
199 changes: 199 additions & 0 deletions src/common/hooks/useFetchSearchData.ts
Original file line number Diff line number Diff line change
@@ -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<StatusEntry[]>) => {
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 };
};
4 changes: 3 additions & 1 deletion src/common/hooks/useLoadSearchResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>) => {
export const useLoadSearchResults = (
fetchData: ({ query, searchBy, offset, selectedSegment, baseQuerySelector }: FetchDataParams) => Promise<void>,
) => {
const { hasSearchParams, defaultSearchBy, defaultQuery, getSearchSourceData, getSearchFacetsData } =
useSearchContext();
const setData = useSetRecoilState(state.search.data);
Expand Down
115 changes: 3 additions & 112 deletions src/common/hooks/useSearch.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
8 changes: 5 additions & 3 deletions src/types/search.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ type NavigationSegment = {
set: Dispatch<SetStateAction<boolean>> | VoidFunction;
};

type EndpointUrlsBySegments = {
[key in SearchSegment]: string;
};

type SearchParams = {
endpointUrl: string;
endpointUrlsBySegments?: {
[key in SearchSegment]: string;
};
endpointUrlsBySegments?: EndpointUrlsBySegments;
primarySegments?: PrimarySegmentsConfig;
searchFilter?: string;
hasSearchParams: boolean;
Expand Down

0 comments on commit fdb1b3d

Please sign in to comment.