Skip to content

Commit

Permalink
feat(searchbox): Support search types: product, category, content, be…
Browse files Browse the repository at this point in the history
…stseller
  • Loading branch information
joerideg committed Jul 29, 2024
1 parent 7e15a8e commit 4b82c0c
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 60 deletions.
54 changes: 0 additions & 54 deletions src/search/hooks/productSearch.hook.ts

This file was deleted.

80 changes: 80 additions & 0 deletions src/search/hooks/search.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
BestsellerOptions,
CategorySearchOptions,
Configuration,
ContentSearchOptions,
ProductSearchOptions,
SearchResponse,
bestseller,
categorySearch,
contentSearch,
productSearch,
} from '@bloomreach/discovery-web-sdk';
import { useEffect, useState } from 'react';
import { SearchType } from '../search-box/search-box.types';

type SearchOptions =
| ProductSearchOptions
| CategorySearchOptions
| ContentSearchOptions
| BestsellerOptions;

type UseSearch = {
response: SearchResponse | null;
loading: boolean;
error: unknown;
};

/**
* Custom hook to perform a product search using Bloomreach Discovery Web SDK.
*
* @param query - The search query string.
* @param configuration - The configuration object for the Bloomreach SDK.
* @param searchOptions - Additional search options excluding the query.
* @returns An object containing the search response, loading state, and any error encountered.
*/
export function useSearch(
searchType: SearchType,
configuration: Configuration,
searchOptions: SearchOptions,
): UseSearch {
const [response, setResponse] = useState<SearchResponse | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<unknown>(null);

useEffect(() => {
const fetchData = async () => {
setLoading(true);

if (searchType === 'product') {
const apiResponse = await productSearch(configuration, searchOptions as ProductSearchOptions);
setResponse(apiResponse);
}

if (searchType === 'category') {
const apiResponse = await categorySearch(configuration, searchOptions as CategorySearchOptions);
setResponse(apiResponse);
}

if (searchType === 'content') {
const apiResponse = await contentSearch(configuration, searchOptions as ContentSearchOptions);
setResponse(apiResponse);
}

if (searchType === 'bestseller') {
const apiResponse = await bestseller(configuration, searchOptions as BestsellerOptions);
setResponse(apiResponse);
}
};

fetchData()
.catch((e: unknown) => {
setError(e);
})
.finally(() => {
setLoading(false);
});
}, [searchType, configuration, searchOptions]);

return { response, loading, error };
}
24 changes: 21 additions & 3 deletions src/search/search-box/search-box.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,42 @@ import {
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { debounce } from '../../utils/debounce';
import { SearchContext } from '../context/search.context';
import { useProductSearch } from '../hooks/productSearch.hook';
import { useSearch } from '../hooks/search.hook';
import { SearchBoxProps } from './search-box.types';
import { SearchResponse } from '@bloomreach/discovery-web-sdk';

type UseSearchBox = {
response: SearchResponse | null;
loading: boolean;
error: unknown;
changeHandler: ChangeEventHandler<HTMLInputElement>;
};

export function useSearchBox(props: SearchBoxProps): UseSearchBox {
const { configuration, searchOptions, debounceDelay } = props;
const { configuration, searchOptions, debounceDelay, searchType } = props;

const [query, setQuery] = useState<string>('');
const debouncedSetQuery = debounce((event: ChangeEvent<HTMLInputElement>) => {
setQuery(event.target.value);
}, debounceDelay ?? 500);
const { response } = useProductSearch(query, configuration, searchOptions);

const memoizedSearchOptions = useMemo(
() => ({
...searchOptions,
q: query,
}),
[query, searchOptions],
);

const { response, error, loading } = useSearch(searchType, configuration, memoizedSearchOptions);

const searchContext = useContext(SearchContext);

useEffect(() => {
if (response) {
searchContext.setSearchResponse(response);
Expand All @@ -34,6 +49,9 @@ export function useSearchBox(props: SearchBoxProps): UseSearchBox {
const changeHandler = useCallback(debouncedSetQuery, [debouncedSetQuery]);

return {
response,
error,
loading,
changeHandler,
};
}
6 changes: 5 additions & 1 deletion src/search/search-box/search-box.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ describe('SearchBox', () => {
it('should render successfully', () => {
const configuration = {} as Configuration;
const options = {} as ProductSearchOptions;
const { container: { firstElementChild } } = render(<SearchBox configuration={configuration} searchOptions={options}/>);
const {
container: { firstElementChild },
} = render(
<SearchBox configuration={configuration} searchOptions={options} searchType="product" />,
);

expect(firstElementChild).toBeTruthy();
});
Expand Down
3 changes: 2 additions & 1 deletion src/search/search-box/search-box.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const Basic: Story = {
{({ searchResponse }) =>
searchResponse?.response?.docs?.map((result) => {
return (
<div>
<div key={result.pid}>
<h2>{result.title}</h2>
<p>{result.description}</p>
</div>
Expand All @@ -58,5 +58,6 @@ export const Basic: Story = {
start: 0,
url: 'https://example.com',
},
searchType: 'product'
},
};
11 changes: 10 additions & 1 deletion src/search/search-box/search-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type { SearchBoxProps } from './search-box.types';
* <SearchBox
* configuration={configuration}
* searchOptions={searchOptions}
* searchType={'product'}
* debounceDelay={300}
* className="test"/>
*
Expand All @@ -56,7 +57,15 @@ import type { SearchBoxProps } from './search-box.types';
*/
export const SearchBox = forwardRef(
(props: SearchBoxProps, forwardedRef: ForwardedRef<HTMLInputElement> | null): ReactElement => {
const { children, className, configuration, searchOptions, debounceDelay, ...rest } = props;
const {
children,
className,
configuration,
searchOptions,
debounceDelay,
searchType,
...rest
} = props;
const { changeHandler } = useSearchBox(props);

return (
Expand Down
10 changes: 10 additions & 0 deletions src/search/search-box/search-box.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,14 @@ export interface SearchBoxProps extends PropsWithChildren {
* The options specific to a Bloormeach search e.g. `q` and `fl`
*/
searchOptions: Omit<ProductSearchOptions, 'q'>;

/**
* The type of search.
*/
searchType: SearchType;
}

/**
* The type of search.
*/
export type SearchType = 'product' | 'category' | 'content' | 'bestseller';

0 comments on commit 4b82c0c

Please sign in to comment.