Help us improve
Share bugs, ideas, or general feedback.
From shopify-commerce
Provides Remix patterns for React apps: loaders/actions, nested routes, error boundaries, form handling, streaming SSR. For Shopify Hydrogen storefronts or Remix apps.
npx claudepluginhub orcaqubits/agentic-commerce-skills-plugins --plugin shopify-commerceHow this skill is triggered — by the user, by Claude, or both
Slash command
/shopify-commerce:react-patternsThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Fetch live docs**:
Build headless Shopify storefronts with Hydrogen: Remix SSR patterns, Oxygen deployment, Storefront API queries, caching strategies, cart, customer accounts, SEO, analytics.
Builds React storefronts for Salesforce PWA Kit with SSR getProps, Commerce SDK hooks, Chakra UI components, client state, and hydration patterns.
Provides React 18/19 patterns for hooks discipline, server/client component boundaries, Suspense, error boundaries, form actions, data fetching, state management decisions, and accessible composition.
Share bugs, ideas, or general feedback.
Fetch live docs:
https://remix.run/docs/en/main for Remix documentationhttps://react.dev/reference/react for React API referencesite:shopify.dev hydrogen remix patterns for Hydrogen-specific patternsShopify's Hydrogen is built on Remix:
Server-side function that runs on every GET request:
import { json, type LoaderFunctionArgs } from '@remix-run/node';
export async function loader({ context, params }: LoaderFunctionArgs) {
const { storefront } = context;
const { products } = await storefront.query(PRODUCTS_QUERY);
return json({ products });
}
export default function ProductsPage() {
const { products } = useLoaderData<typeof loader>();
return <ProductGrid products={products} />;
}
Server-side function for form submissions (POST/PUT/DELETE):
import { redirect, type ActionFunctionArgs } from '@remix-run/node';
export async function action({ request, context }: ActionFunctionArgs) {
const formData = await request.formData();
const variantId = formData.get('variantId') as string;
const { cart } = context;
await cart.addLines([{ merchandiseId: variantId, quantity: 1 }]);
return redirect('/cart');
}
Routes compose via <Outlet>:
app/routes/
├── ($locale)._index.tsx # Homepage
├── ($locale).products._index.tsx # Product listing
├── ($locale).products.$handle.tsx # Product detail
├── ($locale).collections.$handle.tsx # Collection page
├── ($locale).cart.tsx # Cart page
└── ($locale).account.tsx # Account layout
├── ($locale).account._index.tsx # Account dashboard
└── ($locale).account.orders.tsx # Order history
Per-route error handling:
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status}</h1>
<p>{error.statusText}</p>
</div>
);
}
return <div>Something went wrong</div>;
}
| Hook | Purpose |
|---|---|
useState | Local component state |
useEffect | Side effects (client only) |
useRef | Mutable ref / DOM access |
useMemo | Memoized computation |
useCallback | Memoized callback |
useContext | Context consumption |
useReducer | Complex state logic |
| Hook | Purpose |
|---|---|
useLoaderData | Access loader data |
useActionData | Access action response |
useFetcher | Non-navigation data fetching |
useNavigation | Navigation state (loading, submitting) |
useRouteError | Error boundary data |
useSearchParams | URL search parameters |
useParams | Route parameters |
useMatches | All matched routes data |
async/await directlyuseState, no useEffect"use client" directive (in React 19+)useState, useEffect, useRefRemix enhances HTML forms:
import { Form, useNavigation } from '@remix-run/react';
function AddToCartForm({ variantId }: { variantId: string }) {
const navigation = useNavigation();
const isAdding = navigation.state === 'submitting';
return (
<Form method="post" action="/cart">
<input type="hidden" name="variantId" value={variantId} />
<button type="submit" disabled={isAdding}>
{isAdding ? 'Adding...' : 'Add to Cart'}
</button>
</Form>
);
}
For mutations that shouldn't navigate:
function AddToCartButton({ variantId }: { variantId: string }) {
const fetcher = useFetcher();
const isAdding = fetcher.state === 'submitting';
return (
<fetcher.Form method="post" action="/cart">
<input type="hidden" name="variantId" value={variantId} />
<button disabled={isAdding}>
{isAdding ? 'Adding...' : 'Add to Cart'}
</button>
</fetcher.Form>
);
}
Defer non-critical data for faster initial render:
import { defer } from '@remix-run/node';
import { Await, useLoaderData } from '@remix-run/react';
import { Suspense } from 'react';
export async function loader({ context }: LoaderFunctionArgs) {
const criticalData = await context.storefront.query(PRODUCT_QUERY);
const recommendedProducts = context.storefront.query(RECOMMENDATIONS_QUERY);
return defer({
product: criticalData.product,
recommended: recommendedProducts, // not awaited — streams later
});
}
export default function ProductPage() {
const { product, recommended } = useLoaderData<typeof loader>();
return (
<div>
<ProductDetail product={product} />
<Suspense fallback={<Spinner />}>
<Await resolve={recommended}>
{(data) => <RecommendedProducts products={data.products} />}
</Await>
</Suspense>
</div>
);
}
function ProductCard({ product, children }: { product: Product; children?: ReactNode }) {
return (
<article>
<ProductImage image={product.featuredImage} />
<ProductTitle title={product.title} />
<ProductPrice price={product.priceRange} />
{children}
</article>
);
}
function useCart() {
const fetcher = useFetcher();
const addToCart = (variantId: string) => {
fetcher.submit({ variantId }, { method: 'post', action: '/cart' });
};
return { addToCart, isAdding: fetcher.state === 'submitting' };
}
useEffect<Form> over manual fetchuseFetcher for mutations that should not cause navigationdefer() + <Suspense> for non-critical data (recommendations, reviews)useEffect for data fetching — Remix loaders handle thisFetch the Remix and React documentation for exact API signatures, hook behavior, and streaming patterns before implementing.