Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collection Pages & Pagination Refactors #2374

Closed
wants to merge 10 commits into from
104 changes: 104 additions & 0 deletions templates/skeleton/app/components/CollectionPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {Link} from '@remix-run/react';
import {Image, Money} from '@shopify/hydrogen';
import type {ProductItemFragment} from 'storefrontapi.generated';
import {useVariantUrl} from '~/lib/variants';
import {PaginatedResourceSection} from '~/components/PaginatedResourceSection';

export function CollectionPage({
title,
description,
products,
}: {
title: string;
description?: string;
products: React.ComponentProps<
typeof PaginatedResourceSection<ProductItemFragment>
>['connection'];
}) {
return (
<div className="collection">
<h1>{title}</h1>
{description && <p className="collection-description">{description}</p>}
<PaginatedResourceSection
connection={products}
resourcesClassName="products-grid"
>
{({node: product, index}) => (
<ProductItem
key={product.id}
product={product}
loading={index < 8 ? 'eager' : undefined}
/>
)}
</PaginatedResourceSection>
</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="1/1"
data={product.featuredImage}
loading={loading}
sizes="(min-width: 45em) 400px, 100vw"
/>
)}
<h4>{product.title}</h4>
<small>
<Money data={product.priceRange.minVariantPrice} />
</small>
</Link>
);
}

export const PRODUCT_ITEM_FRAGMENT = `#graphql
fragment MoneyProductItem on MoneyV2 {
amount
currencyCode
}
fragment ProductItem on Product {
id
handle
title
featuredImage {
id
altText
url
width
height
}
priceRange {
minVariantPrice {
...MoneyProductItem
}
maxVariantPrice {
...MoneyProductItem
}
}
variants(first: 1) {
nodes {
selectedOptions {
name
value
}
}
}
}
` as const;
42 changes: 42 additions & 0 deletions templates/skeleton/app/components/PaginatedResourceSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import {Pagination} from '@shopify/hydrogen';

/**
* <PaginatedResourceSection > is a component that encapsulate how the previous and next behaviors throughout your application.
*/

export function PaginatedResourceSection<NodesType>({
connection,
children,
resourcesClassName,
}: {
connection: React.ComponentProps<typeof Pagination<NodesType>>['connection'];
children: React.FunctionComponent<{node: NodesType; index: number}>;
resourcesClassName?: string;
}) {
return (
<Pagination connection={connection}>
{({nodes, isLoading, PreviousLink, NextLink}) => {
const resoucesMarkup = nodes.map((node, index) =>
children({node, index}),
);

return (
<div>
<PreviousLink>
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
</PreviousLink>
{resourcesClassName ? (
<div className={resourcesClassName}>{resoucesMarkup}</div>
) : (
resoucesMarkup
)}
<NextLink>
{isLoading ? 'Loading...' : <span>Load more ↓</span>}
</NextLink>
</div>
);
}}
</Pagination>
);
}
2 changes: 1 addition & 1 deletion templates/skeleton/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import favicon from '~/assets/favicon.svg';
import resetStyles from '~/styles/reset.css?url';
import appStyles from '~/styles/app.css?url';
import {PageLayout} from '~/components/PageLayout';
import {PageLayout} from './components/layout/PageLayout';
import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments';

export type RootLoader = typeof loader;
Expand Down
23 changes: 5 additions & 18 deletions templates/skeleton/app/routes/account.orders._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import {
getPaginationVariables,
flattenConnection,
} from '@shopify/hydrogen';
import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {CUSTOMER_ORDERS_QUERY} from '~/graphql/customer-account/CustomerOrdersQuery';
import type {
CustomerOrdersFragment,
OrderItemFragment,
} from 'customer-accountapi.generated';
import {PaginatedResourceSection} from '~/components/sections/PaginatedResourceSection';

export const meta: MetaFunction = () => {
return [{title: 'Orders'}];
Expand Down Expand Up @@ -51,23 +52,9 @@ function OrdersTable({orders}: Pick<CustomerOrdersFragment, 'orders'>) {
return (
<div className="acccount-orders">
{orders?.nodes.length ? (
<Pagination connection={orders}>
{({nodes, isLoading, PreviousLink, NextLink}) => {
return (
<>
<PreviousLink>
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
</PreviousLink>
{nodes.map((order) => {
return <OrderItem key={order.id} order={order} />;
})}
<NextLink>
{isLoading ? 'Loading...' : <span>Load more ↓</span>}
</NextLink>
</>
);
}}
</Pagination>
<PaginatedResourceSection connection={orders}>
{({node: order}) => <OrderItem key={order.id} order={order} />}
</PaginatedResourceSection>
) : (
<EmptyOrders />
)}
Expand Down
33 changes: 10 additions & 23 deletions templates/skeleton/app/routes/blogs.$blogHandle._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {Link, useLoaderData, type MetaFunction} from '@remix-run/react';
import {Image, Pagination, getPaginationVariables} from '@shopify/hydrogen';
import type {ArticleItemFragment} from 'storefrontapi.generated';
import {PaginatedResourceSection} from '~/components/sections/PaginatedResourceSection';

export const meta: MetaFunction<typeof loader> = ({data}) => {
return [{title: `Hydrogen | ${data?.blog.title ?? ''} blog`}];
Expand Down Expand Up @@ -68,29 +69,15 @@ export default function Blog() {
<div className="blog">
<h1>{blog.title}</h1>
<div className="blog-grid">
<Pagination connection={articles}>
{({nodes, isLoading, PreviousLink, NextLink}) => {
return (
<>
<PreviousLink>
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
</PreviousLink>
{nodes.map((article, index) => {
return (
<ArticleItem
article={article}
key={article.id}
loading={index < 2 ? 'eager' : 'lazy'}
/>
);
})}
<NextLink>
{isLoading ? 'Loading...' : <span>Load more ↓</span>}
</NextLink>
</>
);
}}
</Pagination>
<PaginatedResourceSection connection={articles}>
{({node: article, index}) => (
<ArticleItem
article={article}
key={article.id}
loading={index < 2 ? 'eager' : 'lazy'}
/>
)}
</PaginatedResourceSection>
</div>
</div>
);
Expand Down
39 changes: 13 additions & 26 deletions templates/skeleton/app/routes/blogs._index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {Link, useLoaderData, type MetaFunction} from '@remix-run/react';
import {Pagination, getPaginationVariables} from '@shopify/hydrogen';
import {PaginatedResourceSection} from '~/components/sections/PaginatedResourceSection';

export const meta: MetaFunction = () => {
return [{title: `Hydrogen | Blogs`}];
Expand Down Expand Up @@ -53,32 +54,18 @@ export default function Blogs() {
<div className="blogs">
<h1>Blogs</h1>
<div className="blogs-grid">
<Pagination connection={blogs}>
{({nodes, isLoading, PreviousLink, NextLink}) => {
return (
<>
<PreviousLink>
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
</PreviousLink>
{nodes.map((blog) => {
return (
<Link
className="blog"
key={blog.handle}
prefetch="intent"
to={`/blogs/${blog.handle}`}
>
<h2>{blog.title}</h2>
</Link>
);
})}
<NextLink>
{isLoading ? 'Loading...' : <span>Load more ↓</span>}
</NextLink>
</>
);
}}
</Pagination>
<PaginatedResourceSection connection={blogs}>
{({node: blog}) => (
<Link
className="blog"
key={blog.handle}
prefetch="intent"
to={`/blogs/${blog.handle}`}
>
<h2>{blog.title}</h2>
</Link>
)}
</PaginatedResourceSection>
</div>
</div>
);
Expand Down
Loading
Loading