Skip to content

Instantly share code, notes, and snippets.

@scottdixon
Created November 21, 2024 03:49
Show Gist options
  • Save scottdixon/69364319c6a02d0bebe019306e3effae to your computer and use it in GitHub Desktop.
Save scottdixon/69364319c6a02d0bebe019306e3effae to your computer and use it in GitHub Desktop.
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