Skip to content

Commit

Permalink
Support multiple Pagination components on the same page (#2649)
Browse files Browse the repository at this point in the history
  • Loading branch information
scottdixon authored Dec 10, 2024
1 parent 91d60fd commit 8f64915
Show file tree
Hide file tree
Showing 6 changed files with 601 additions and 60 deletions.
9 changes: 9 additions & 0 deletions .changeset/metal-wasps-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@shopify/hydrogen': minor
---

Added namespace support to prevent conflicts when using multiple Pagination components:
- New optional `namespace` prop for the `<Pagination/>` component
- New optional `namespace` option for `getPaginationVariables()` utility
- When specified, pagination URL parameters are prefixed with the namespace (e.g., `products_cursor` instead of `cursor`)
- Maintains backwards compatibility when no namespace is provided
29 changes: 29 additions & 0 deletions packages/hydrogen/src/pagination/Pagination.doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,35 @@ const data: ReferenceEntityTemplateSchema = {
description: '',
},
],
examples: {
description: 'Other examples using the `Pagination` component.',
exampleGroups: [
{
title: 'Multiple `Pagination` components on a single page',
examples: [
{
description:
'Use the `namespace` prop to differentiate between multiple `Pagination` components on a single page',
codeblock: {
title: 'Example',
tabs: [
{
title: 'JavaScript',
code: './Pagination.multiple.example.jsx',
language: 'jsx',
},
{
title: 'TypeScript',
code: './Pagination.multiple.example.tsx',
language: 'tsx',
},
],
},
},
],
},
],
},
};

export default data;
117 changes: 117 additions & 0 deletions packages/hydrogen/src/pagination/Pagination.multiple.example.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {json} from '@shopify/remix-oxygen';
import {useLoaderData, Link} from '@remix-run/react';
import {getPaginationVariables, Pagination} from '@shopify/hydrogen';

export async function loader({request, context: {storefront}}) {
const womensPaginationVariables = getPaginationVariables(request, {
pageBy: 2,
namespace: 'womens', // Specify a unique namespace for the pagination parameters
});
const mensPaginationVariables = getPaginationVariables(request, {
pageBy: 2,
namespace: 'mens', // Specify a unique namespace for the pagination parameters
});

const [womensProducts, mensProducts] = await Promise.all([
storefront.query(COLLECTION_PRODUCTS_QUERY, {
variables: {...womensPaginationVariables, handle: 'women'},
}),
storefront.query(COLLECTION_PRODUCTS_QUERY, {
variables: {...mensPaginationVariables, handle: 'men'},
}),
]);

return json({womensProducts, mensProducts});
}

export default function Collection() {
const {womensProducts, mensProducts} = useLoaderData();
return (
<div className="collection">
<h1>Womens</h1>

<Pagination
connection={womensProducts?.collection?.products}
// Specify a unique namespace for the pagination links
namespace="womens"
>
{({nodes, isLoading, PreviousLink, NextLink}) => {
return (
<div>
<PreviousLink>
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
</PreviousLink>
<div>
{nodes.map((product) => (
<div key={product.id}>
<Link to={`/products/${product.handle}`}>
{product.title}
</Link>
</div>
))}
</div>
<NextLink>
{isLoading ? 'Loading...' : <span>Load more ↓</span>}
</NextLink>
</div>
);
}}
</Pagination>

<h1>Mens</h1>
<Pagination
connection={mensProducts?.collection?.products}
// Specify a unique namespace for the pagination links
namespace="mens"
>
{({nodes, isLoading, PreviousLink, NextLink}) => {
return (
<div>
<PreviousLink>
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
</PreviousLink>
<div>
{nodes.map((product) => (
<div key={product.id}>
<Link to={`/products/${product.handle}`}>
{product.title}
</Link>
</div>
))}
</div>
<NextLink>
{isLoading ? 'Loading...' : <span>Load more ↓</span>}
</NextLink>
</div>
);
}}
</Pagination>
</div>
);
}

const COLLECTION_PRODUCTS_QUERY = `#graphql
query CollectionProducts(
$first: Int
$last: Int
$startCursor: String
$endCursor: String
$handle: String!
) {
collection(handle: $handle) {
products(first: $first, last: $last, before: $startCursor, after: $endCursor) {
nodes {
id
handle
title
}
pageInfo {
hasPreviousPage
hasNextPage
startCursor
endCursor
}
}
}
}
`;
121 changes: 121 additions & 0 deletions packages/hydrogen/src/pagination/Pagination.multiple.example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {useLoaderData, Link} from '@remix-run/react';
import {getPaginationVariables, Pagination} from '@shopify/hydrogen';
import {type Collection} from '@shopify/hydrogen-react/storefront-api-types';

export async function loader({
request,
context: {storefront},
}: LoaderFunctionArgs) {
const womensPaginationVariables = getPaginationVariables(request, {
pageBy: 2,
namespace: 'womens', // Specify a unique namespace for the pagination parameters
});
const mensPaginationVariables = getPaginationVariables(request, {
pageBy: 2,
namespace: 'mens', // Specify a unique namespace for the pagination parameters
});

const [womensProducts, mensProducts] = await Promise.all([
storefront.query<{collection: Collection}>(COLLECTION_PRODUCTS_QUERY, {
variables: {...womensPaginationVariables, handle: 'women'},
}),
storefront.query<{collection: Collection}>(COLLECTION_PRODUCTS_QUERY, {
variables: {...mensPaginationVariables, handle: 'men'},
}),
]);

return json({womensProducts, mensProducts});
}

export default function Collection() {
const {womensProducts, mensProducts} = useLoaderData<typeof loader>();
return (
<div className="collection">
<h1>Womens</h1>

<Pagination
connection={womensProducts?.collection?.products}
// Specify a unique namespace for the pagination links
namespace="womens"
>
{({nodes, isLoading, PreviousLink, NextLink}) => {
return (
<div>
<PreviousLink>
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
</PreviousLink>
<div>
{nodes.map((product) => (
<div key={product.id}>
<Link to={`/products/${product.handle}`}>
{product.title}
</Link>
</div>
))}
</div>
<NextLink>
{isLoading ? 'Loading...' : <span>Load more ↓</span>}
</NextLink>
</div>
);
}}
</Pagination>

<h1>Mens</h1>
<Pagination
connection={mensProducts?.collection?.products}
// Specify a unique namespace for the pagination links
namespace="mens"
>
{({nodes, isLoading, PreviousLink, NextLink}) => {
return (
<div>
<PreviousLink>
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
</PreviousLink>
<div>
{nodes.map((product) => (
<div key={product.id}>
<Link to={`/products/${product.handle}`}>
{product.title}
</Link>
</div>
))}
</div>
<NextLink>
{isLoading ? 'Loading...' : <span>Load more ↓</span>}
</NextLink>
</div>
);
}}
</Pagination>
</div>
);
}

const COLLECTION_PRODUCTS_QUERY = `#graphql
query CollectionProducts(
$first: Int
$last: Int
$startCursor: String
$endCursor: String
$handle: String!
) {
collection(handle: $handle) {
products(first: $first, last: $last, before: $startCursor, after: $endCursor) {
nodes {
id
handle
title
}
pageInfo {
hasPreviousPage
hasNextPage
startCursor
endCursor
}
}
}
}
` as const;
Loading

0 comments on commit 8f64915

Please sign in to comment.