Search Results Usage
- Implementation on file
\app\routes\search.tsx
- The next example only handles the Search Page.
Configuration
getSearchResults
function
To customize the behavior of the getSearchResults
function, you can pass the specs
parameter to control aspects like:
query
- Search term (String)page
- Page number you want to fetch (Number)narrow
- Filtered data (Narrow)facetsRequired
- If facets are wanted with the response (boolean)productsPerPage
- Number of products per page (Number)sortBy
- Sort option (Sorting)searchWithinResultsQuery
- Internal search term in that Search term results (String)
Return Object
- Promise of an Object type SearchResponse
Usage Example
Imports
import {Analytics, CacheLong, Image, Money} from '@shopify/hydrogen';
import {PaginationBar, transformToShopifyStructure} from '@fast-simon/storefront-kit';
import {Narrow} from '@fast-simon/utilities';
... // additional imports
Loader function
export async function loader({request, context}: LoaderFunctionArgs) {
const url = new URL(request.url);
const searchPromise = regularSearch({request, context});
searchPromise.catch((error: Error) => {
console.error(error);
return {term: '', result: null, error: error.message};
});
const data = await searchPromise;
const facets = {facets: data.result.getFacetsOnly ? data.result.getFacetsOnly() : data.result.facets};
return defer({...data, ...facets});
}
async function regularSearch({
request,
context,
}: Pick<
LoaderFunctionArgs,
'request' | 'context'
>): Promise<RegularSearchReturn> {
const {fastSimon} = context;
const url = new URL(request.url);
const term = String(url.searchParams.get('q') || '');
const page = Number(url.searchParams.get('page') || 1);
const sortBy = url.searchParams.get('sort');
const narrowString = url.searchParams.get('filters');
const narrow = narrowString ? Narrow.toServerNarrow(Narrow.parseNarrow(narrowString || '')) : []
const fastSimonSearchResults = await fastSimon.getSearchResults({
props: {
page: page,
narrow: narrow,
facetsRequired: 1,
query: term,
productsPerPage: 20,
sortBy: sortBy
}
});
if (!fastSimonSearchResults) {
throw new Error('No search data returned from FastSimon API');
}
const transformed = transformToShopifyStructure(fastSimonSearchResults.items);
const facets = fastSimonSearchResults.getFacetsOnly ? fastSimonSearchResults.getFacetsOnly() : {};
return {type: 'regular', term, error: undefined, result: {total: fastSimonSearchResults.total_results, ...fastSimonSearchResults, ...facets, items: transformed}};
}
... // more functions and logic
SearchPage Component
export default function SearchPage() {
const {type, term, result, error, facets, dashboardConfig} = useLoaderData<typeof loader>();
if (type === 'predictive') return null;
return (
<div className="search">
<h1>Search</h1>
<SearchForm>
{({inputRef}) => (
<>
<input
defaultValue={term}
name="q"
placeholder="Search…"
ref={inputRef}
type="search"
/>
<button type="submit" className={'search-button'}>Search</button>
</>
)}
</SearchForm>
{error && <p style={{color: 'red'}}>{error}</p>}
{!term || !result?.total ? (
<SearchResults.Empty />
) : (
<>
<div className={"results-filters-container"}>
<div className={'fs-products-summary'}>
<ProductsGrid products={result.items.products.nodes} />
</div>
</div>
<br />
<PaginationBar total={result.total_p} />
</>
)}
<Analytics.SearchView data={{searchTerm: term, searchResults: result}} />
</div>
);
}
function ProductsGrid({products}) {
return (
<div className="products-grid">
{products.map((product, index) => {
return (
<ProductItem
key={product.id}
product={product}
loading={index < 8 ? 'eager' : undefined}
/>
);
})}
</div>
);
}
function ProductItem({
product,
loading,
}: {
product: ProductItemFragment;
loading?: 'eager' | 'lazy';
}) {
const variant = product.variants.nodes[0];
const variantUrl = useVariantUrl(product.handle, variant.selectedOptions);
return (
<Link
className="product-item"
key={product.id}
prefetch="intent"
to={variantUrl}
>
{product.featuredImage && (
<Image
alt={product.featuredImage.altText || product.title}
aspectRatio="0.714"
data={product.featuredImage}
loading={loading}
sizes="(min-width: 45em) 400px, 100vw"
/>
)}
<h4>{product.title}</h4>
<small>
<Money data={product.priceRange.minVariantPrice} />
</small>
</Link>
);
}
... // more functions and logic