Skip to content

Commit

Permalink
Add <Pagination /> to /search route (#918)
Browse files Browse the repository at this point in the history
* add Pagination to search results

* add changeset
  • Loading branch information
juanpprieto authored May 19, 2023
1 parent 6ef8753 commit fb42061
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 81 deletions.
5 changes: 5 additions & 0 deletions .changeset/itchy-dryers-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'demo-store': patch
---

Adds pagination support on /search results
202 changes: 121 additions & 81 deletions templates/demo-store/app/routes/($locale).search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,43 @@ import type {
ProductConnection,
} from '@shopify/hydrogen/storefront-api-types';
import {Suspense} from 'react';
import invariant from 'tiny-invariant';
import {
Pagination__unstable as Pagination,
getPaginationVariables__unstable as getPaginationVariables,
} from '@shopify/hydrogen';

import {
FeaturedCollections,
Grid,
Heading,
Input,
PageHeader,
ProductGrid,
ProductCard,
ProductSwimlane,
FeaturedCollections,
Section,
Text,
} from '~/components';
import {PRODUCT_CARD_FRAGMENT} from '~/data/fragments';
import {PAGINATION_SIZE} from '~/lib/const';
import {PRODUCT_CARD_FRAGMENT} from '~/data/fragments';
import {getImageLoadingPriority} from '~/lib/const';
import {seoPayload} from '~/lib/seo.server';

export async function loader({request, context: {storefront}}: LoaderArgs) {
const searchParams = new URL(request.url).searchParams;
const cursor = searchParams.get('cursor')!;
const searchTerm = searchParams.get('q')!;
const variables = getPaginationVariables(request, {pageBy: 8});

const data = await storefront.query<{
const {products} = await storefront.query<{
products: ProductConnection;
}>(SEARCH_QUERY, {
variables: {
pageBy: PAGINATION_SIZE,
searchTerm,
after: cursor,
...variables,
country: storefront.i18n.country,
language: storefront.i18n.language,
},
});

invariant(data, 'No data returned from Shopify API');
const {products} = data;

const getRecommendations = !searchTerm || products?.nodes?.length === 0;
const seoCollection = {
id: 'search',
Expand Down Expand Up @@ -93,76 +94,137 @@ export default function Search() {
<Form method="get" className="relative flex w-full text-heading">
<Input
defaultValue={searchTerm}
name="q"
placeholder="Search…"
type="search"
variant="search"
name="q"
/>
<button className="absolute right-0 py-2" type="submit">
Go
</button>
</Form>
</PageHeader>
{!searchTerm || noResults ? (
<>
{noResults && (
<Section padding="x">
<Text className="opacity-50">
No results, try something else.
</Text>
</Section>
)}
<Suspense>
<Await
errorElement="There was a problem loading related products"
resolve={noResultRecommendations}
<NoResults
noResults={noResults}
recommendations={
noResultRecommendations as ReturnType<
typeof getNoResultRecommendations
>
{(data) => (
<>
<FeaturedCollections
title="Trending Collections"
collections={
data!.featuredCollections as SerializeFrom<Collection[]>
}
/>
<ProductSwimlane
title="Trending Products"
products={
data!.featuredProducts as SerializeFrom<Product[]>
}
/>
</>
)}
</Await>
</Suspense>
</>
}
/>
) : (
<Section>
<ProductGrid
key="search"
url={`/search?q=${searchTerm}`}
collection={{products} as Collection}
/>
<Pagination connection={products}>
{({nodes, isLoading, NextLink, PreviousLink}) => {
const itemsMarkup = nodes.map((product, i) => (
<ProductCard
key={product.id}
product={product}
loading={getImageLoadingPriority(i)}
/>
));

return (
<>
<div className="flex items-center justify-center mt-6">
<PreviousLink className="inline-block rounded font-medium text-center py-3 px-6 border border-primary/10 bg-contrast text-primary w-full">
{isLoading ? 'Loading...' : 'Previous'}
</PreviousLink>
</div>
<Grid data-test="product-grid">{itemsMarkup}</Grid>
<div className="flex items-center justify-center mt-6">
<NextLink className="inline-block rounded font-medium text-center py-3 px-6 border border-primary/10 bg-contrast text-primary w-full">
{isLoading ? 'Loading...' : 'Next'}
</NextLink>
</div>
</>
);
}}
</Pagination>
</Section>
)}
</>
);
}

function NoResults({
noResults,
recommendations,
}: {
noResults: boolean;
recommendations: ReturnType<typeof getNoResultRecommendations>;
}) {
return (
<>
{noResults && (
<Section padding="x">
<Text className="opacity-50">
No results, try a different search.
</Text>
</Section>
)}
<Suspense>
<Await
errorElement="There was a problem loading related products"
resolve={recommendations}
>
{({featuredCollections, featuredProducts}) => (
<>
<FeaturedCollections
title="Trending Collections"
collections={featuredCollections as SerializeFrom<Collection[]>}
/>
<ProductSwimlane
title="Trending Products"
products={featuredProducts as SerializeFrom<Product[]>}
/>
</>
)}
</Await>
</Suspense>
</>
);
}

export async function getNoResultRecommendations(
storefront: LoaderArgs['context']['storefront'],
) {
const {featuredProducts, featuredCollections} = await storefront.query<{
featuredProducts: ProductConnection;
featuredCollections: CollectionConnection;
}>(SEARCH_NO_RESULTS_QUERY, {
variables: {
pageBy: PAGINATION_SIZE,
country: storefront.i18n.country,
language: storefront.i18n.language,
},
});

return {
featuredCollections: flattenConnection(featuredCollections),
featuredProducts: flattenConnection(featuredProducts),
};
}

const SEARCH_QUERY = `#graphql
${PRODUCT_CARD_FRAGMENT}
query search(
$searchTerm: String
query PaginatedProductsSearch(
$country: CountryCode
$endCursor: String
$first: Int
$language: LanguageCode
$pageBy: Int!
$after: String
$last: Int
$searchTerm: String
$startCursor: String
) @inContext(country: $country, language: $language) {
products(
first: $pageBy
sortKey: RELEVANCE
first: $first,
last: $last,
before: $startCursor,
after: $endCursor,
sortKey: RELEVANCE,
query: $searchTerm
after: $after
) {
nodes {
...ProductCard
Expand All @@ -175,32 +237,11 @@ const SEARCH_QUERY = `#graphql
}
}
}
`;

export async function getNoResultRecommendations(
storefront: LoaderArgs['context']['storefront'],
) {
const data = await storefront.query<{
featuredCollections: CollectionConnection;
featuredProducts: ProductConnection;
}>(SEARCH_NO_RESULTS_QUERY, {
variables: {
pageBy: PAGINATION_SIZE,
country: storefront.i18n.country,
language: storefront.i18n.language,
},
});

invariant(data, 'No data returned from Shopify API');

return {
featuredCollections: flattenConnection(data.featuredCollections),
featuredProducts: flattenConnection(data.featuredProducts),
};
}
` as const;

const SEARCH_NO_RESULTS_QUERY = `#graphql
query searchNoResult(
${PRODUCT_CARD_FRAGMENT}
query NoSearchResults(
$country: CountryCode
$language: LanguageCode
$pageBy: Int!
Expand All @@ -224,5 +265,4 @@ const SEARCH_NO_RESULTS_QUERY = `#graphql
}
}
}
${PRODUCT_CARD_FRAGMENT}
`;
` as const;

0 comments on commit fb42061

Please sign in to comment.