-
-
Save scottdixon/69364319c6a02d0bebe019306e3effae to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; | |
import {useLoaderData, Link, type MetaFunction} from '@remix-run/react'; | |
import {getPaginationVariables, Image, Money} from '@shopify/hydrogen'; | |
import type {ProductItemFragment} from 'storefrontapi.generated'; | |
import {useVariantUrl} from '~/lib/variants'; | |
import {PaginatedResourceSection} from '~/components/PaginatedResourceSection'; | |
export const meta: MetaFunction<typeof loader> = () => { | |
return [{title: `Hydrogen | Products`}]; | |
}; | |
export async function loader(args: LoaderFunctionArgs) { | |
// Start fetching non-critical data without blocking time to first byte | |
const deferredData = loadDeferredData(args); | |
// Await the critical data required to render initial state of the page | |
const criticalData = await loadCriticalData(args); | |
return defer({...deferredData, ...criticalData}); | |
} | |
/** | |
* Load data necessary for rendering content above the fold. This is the critical data | |
* needed to render the page. If it's unavailable, the whole page should 400 or 500 error. | |
*/ | |
async function loadCriticalData({context, request}: LoaderFunctionArgs) { | |
const {storefront} = context; | |
const paginationVariables = getPaginationVariables(request, { | |
pageBy: 3, | |
}); | |
const paginationVariables2 = getPaginationVariables(request, { | |
pageBy: 3, | |
namespace: 'products2', | |
}); | |
const [{products}, {products: products2}] = await Promise.all([ | |
storefront.query(CATALOG_QUERY, { | |
variables: {...paginationVariables}, | |
}), | |
storefront.query(CATALOG_QUERY_2, { | |
variables: {...paginationVariables2}, | |
}), | |
// Add other queries here, so that they are loaded in parallel | |
]); | |
return {products, products2}; | |
} | |
/** | |
* Load data for rendering content below the fold. This data is deferred and will be | |
* fetched after the initial page load. If it's unavailable, the page should still 200. | |
* Make sure to not throw any errors here, as it will cause the page to 500. | |
*/ | |
function loadDeferredData({context}: LoaderFunctionArgs) { | |
return {}; | |
} | |
export default function Collection() { | |
const {products, products2} = useLoaderData<typeof loader>(); | |
return ( | |
<div className="collection"> | |
<h1>Products 1</h1> | |
<PaginatedResourceSection | |
connection={products} | |
resourcesClassName="products-grid" | |
> | |
{({node: product, index}) => ( | |
<span key={`product-${product.id}`}>{product.title}</span> | |
)} | |
</PaginatedResourceSection> | |
<h1>Products 2</h1> | |
<PaginatedResourceSection | |
connection={products2} | |
resourcesClassName="products-grid" | |
namespace="products2" | |
> | |
{({node: product, index}) => ( | |
<span key={`product2-${product.id}`}>{product.title}</span> | |
)} | |
</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> | |
); | |
} | |
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; | |
// NOTE: https://shopify.dev/docs/api/storefront/2024-01/objects/product | |
const CATALOG_QUERY = `#graphql | |
query Catalog( | |
$country: CountryCode | |
$language: LanguageCode | |
$first: Int | |
$last: Int | |
$startCursor: String | |
$endCursor: String | |
) @inContext(country: $country, language: $language) { | |
products(first: $first, last: $last, before: $startCursor, after: $endCursor) { | |
nodes { | |
...ProductItem | |
} | |
pageInfo { | |
hasPreviousPage | |
hasNextPage | |
startCursor | |
endCursor | |
} | |
} | |
} | |
${PRODUCT_ITEM_FRAGMENT} | |
` as const; | |
const CATALOG_QUERY_2 = `#graphql | |
query Catalog2( | |
$country: CountryCode | |
$language: LanguageCode | |
$first: Int | |
$last: Int | |
$startCursor: String | |
$endCursor: String | |
) @inContext(country: $country, language: $language) { | |
products(first: $first, last: $last, before: $startCursor, after: $endCursor) { | |
nodes { | |
...ProductItem | |
} | |
pageInfo { | |
hasPreviousPage | |
hasNextPage | |
startCursor | |
endCursor | |
} | |
} | |
} | |
${PRODUCT_ITEM_FRAGMENT} | |
` as const; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment