@pack/react: React Component Library
The @pack/react package provides SDKs and components to enable your React app to connect with Pack.
For release notes on every version, see the @pack/react changelog.
Quick Start
Install with a package manager:
npm install @pack/react
Here is a list of exports from this package:
import {
PackProvider,
PreviewProvider,
RenderSection,
RenderSections,
registerSection,
registerStorefrontSettingsSchema,
setPackConsent,
useLocale,
useOverlayScript,
usePackContext,
usePlaybookCart,
useSections,
useSiteSettings,
} from '@pack/react'
import type {PlaybookCartAdd, PlaybookCartInput} from '@pack/react'
| Export | Description |
|---|---|
PackProvider | Root provider for site settings, preview mode, and the Customizer connection. |
PreviewProvider | Alias of PackProvider, kept for existing storefronts. |
registerSection | Registers a React component as a section. |
registerStorefrontSettingsSchema | Registers site settings schemas for the Customizer. |
setPackConsent | Records the visitor's consent with Pack, for consent tools other than Shopify's privacy banner. |
RenderSections | Renders all sections of a page. |
RenderSection | Renders a single section, e.g. a nested section inside a blocks field. |
useSections | Returns the rendered section elements instead of rendering them. |
useSiteSettings | Returns the current site settings. |
useLocale | Returns the current { language, country } locale from PackProvider, or null. |
usePackContext | Returns the raw Pack context (isPreview, customizerMeta, siteSettings, liveContent, contentEnvironment, locale). |
useOverlayScript | Injects the Customizer overlay script in preview mode. Called for you by PackProvider. |
usePlaybookCart | Opt-in bridge that lets Playbook's "Add to cart" use your storefront cart. |
<PackProvider />
The <PackProvider /> component is a React provider for your app and gives context for important info such as siteSettings, preview mode, and the Customizer connection. PreviewProvider is an alias of PackProvider, so existing code using <PreviewProvider /> keeps working.
<PackProvider/> props
interface PackProviderProps {
children: ReactNode;
siteSettings: any;
isPreviewModeEnabled?: boolean;
customizerMeta?: {
overlay?: { src?: string; version?: string };
previewContext?: PreviewContext;
[key: string]: any;
};
contentEnvironment?: string;
locale?: { language: string; country: string } | null;
liveContent?: any;
}
Example
// app/root.tsx
import {Links, Meta, Outlet, Scripts, useLoaderData} from 'react-router';
import {PackProvider} from '@pack/react';
import type {Route} from './+types/root';
export async function loader({context}: Route.LoaderArgs) {
const {pack} = context;
// Pass the full query result: useSiteSettings reads siteSettings.data.siteSettings
const siteSettings = await pack.query(SITE_SETTINGS_QUERY, {});
return {
customizerMeta: pack.session.get('customizerMeta'),
isPreviewModeEnabled: pack.isPreviewModeEnabled(),
siteSettings,
};
}
export default function App() {
const {customizerMeta, isPreviewModeEnabled, siteSettings} =
useLoaderData<typeof loader>();
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<PackProvider
customizerMeta={customizerMeta}
isPreviewModeEnabled={isPreviewModeEnabled}
siteSettings={siteSettings}
>
<Outlet />
</PackProvider>
<Scripts />
</body>
</html>
);
}
Preview toolbar
When isPreviewModeEnabled is true, PackProvider renders a preview toolbar on the storefront. As of @pack/react 4.5.0 this is a draggable pill that docks to any viewport edge (the position is saved in localStorage under pack-preview-toolbar-dock) and opens a read-only panel with the active preview link, content release, environment, locale, A/B test, and drafts. Nothing is mounted when preview mode is off.
4.5.0 removed the old preview toast. If your CSS or tests target
[data-pack-preview-toast] or [data-pack-preview-toast-content], switch to
[data-pack-preview-toolbar], [data-pack-preview-toolbar-pill],
[data-pack-preview-toolbar-panel], and [data-pack-preview-toolbar-hide].
Consent from Shopify's privacy banner
Since @pack/react 4.6.0, PackProvider forwards the visitor's answer to Shopify's privacy banner to Pack, once the Customer Privacy API loads and whenever the visitor answers. Purposes the visitor hasn't answered are not sent. This takes effect with @pack/hydrogen 4.0, which records the answer; on earlier versions it does nothing. See Consent Management.
window.packDebug
Since @pack/react 4.2.0, PackProvider loads a window.packDebug helper in the browser. No setup is needed.
window.packDebug.getTestInfos() // [{ test, variant, expiresAt }], every test the visitor is in (4.6.0)
window.packDebug.getTestInfo() // the oldest of those, or null
window.packDebug.getTestRulesCacheInfo() // { rulesCount, cachedAt, ageSeconds, isFresh } or null
See Debugging with packDebug for details.
setPackConsent()
Records the visitor's consent with Pack, for consent tools other than Shopify's privacy banner, such as OneTrust or Cookiebot. Call it when the page loads and whenever the visitor changes their answer. Added in @pack/react 4.6.0; requires @pack/hydrogen 4.0.
setPackConsent(update: {
analytics?: boolean;
marketing?: boolean;
preferences?: boolean;
saleOfData?: boolean;
}): Promise<void>
Purposes you leave out keep their current answer, and answers that already match what Pack has recorded are not sent again. The server sets the __pack_user_consent cookie. If the storefront isn't running @pack/hydrogen 4.0, calls stop for 30 minutes and then retry. See Consent Management for a OneTrust example.
registerSection()
registerSection is a function that will register your React components to become Sections. If you create a component and want to make it an editable section on your storefront, it is important to register it with this function.
Usage
Here at Pack, we recommend creating a sections/ folder in the project where all your sections will live. This sections/ folder will export a registerSections utility function that makes it easy to register all your sections in the root of your app.
Here is an example from our Blueprint Theme of how to use registerSections() in your storefront's root.tsx.
Arguments
- Name
section- Type
- React.ComponentType<any> & { Schema?: Schema }
- Description
The React component that you want to become an interactable section in your storefront and the Customizer.
- Name
config- Type
- { name: string }
- Description
nameis a unique key for this section. It must match the section schema'skey, which is stored as the section's_template.
Example
import { registerSection } from '@pack/react';
import { Hero } from './Hero';
import { FiftyFiftyHero } from './FiftyFiftyHero';
import { TextBlock } from './TextBlock';
import { Image } from './Image';
import { HTML } from './HTML';
export { Hero, FiftyFiftyHero, TextBlock };
export function registerSections() {
registerSection(Hero, { name: 'hero' });
registerSection(FiftyFiftyHero, { name: 'fifty-fifty-hero' });
registerSection(TextBlock, { name: 'text-block' });
registerSection(Image, { name: 'image-block' });
registerSection(HTML, { name: 'html-block' });
}
<RenderSections />
The <RenderSections /> component will take in your page content data and return an array of React components that you have registered as sections.
In addition to the section data needed for rendering, the content query must
return handle and template { type }. Pack uses these fields in preview
mode to tell the Customizer which page to load. If template.type is omitted,
the storefront can still render while the Customizer sidebar remains stuck
loading.
Example
// routes/_index.tsx
import { useLoaderData } from 'react-router';
import { RenderSections } from '@pack/react';
import type { Route } from './+types/_index';
export async function loader({ context }: Route.LoaderArgs) {
const { data } = await context.pack.query(HOME_PAGE_QUERY, {});
return {
page: data.page,
};
}
export default function Index() {
const { page } = useLoaderData<typeof loader>();
return (
<div className="grid gap-4">
<RenderSections content={page} />
</div>
);
}
Each section renders as a <style> element with its element styles followed by <section data-comp="{template}" data-comp-id="{id}">. Sections with sectionVisibility: 'hidden' or an unregistered _template are skipped.
<RenderSection />
Added in @pack/react 4.3.0. <RenderSection /> renders a single section against the registry populated by registerSection. Use it inside a parent section whose blocks field allows nested sections via sectionTypes. For full-page rendering, keep using <RenderSections />.
Props
- Name
section- Type
- any
- Description
The section node to render. Must carry an
idorclientId. The component is looked up by its_template.
Example
import { RenderSection } from '@pack/react';
export function NestedBlock({ block }) {
if (block._template === 'heading') {
return <h2>{block.text}</h2>;
}
// Registered section types from `sectionTypes`
return <RenderSection section={block} />;
}
useSections()
useSections resolves page content the same way as <RenderSections /> (including live Customizer updates in preview mode) but returns the rendered elements so you can reorder, wrap, or interleave them.
function useSections(props: { content?: any }): ReactElement[] | null
It returns an array with one React.Fragment per visible, registered section (keyed by section id), or null when there is no content. Each fragment's props.children is a flat pair: the <style> element followed by the <section data-comp> element.
@pack/react 4.3.0 briefly wrapped these children in a RenderSection
element. 4.3.1 restored the flat <style> + <section data-comp> children,
so upgrade to 4.3.1 or later if you walk fragment.props.children to find a
section.
Example
import { useSections } from '@pack/react';
export function PageSections({ page }) {
const sections = useSections({ content: page });
if (!sections) return null;
const [first, ...rest] = sections;
return (
<>
{first}
<Banner />
{rest}
</>
);
}
registerStorefrontSettingsSchema()
Similar to registerSection, the registerStorefrontSettingsSchema is a function that will register your site settings schema to be editable within the Customizer. This will allow you to create a global setting for your store to be used for headers, footers, etc. You can access these settings by using the useSiteSettings hook.
Usage
Here at Pack, we recommend creating a settings/ folder in the project where all your site settings will live. This settings/ folder will export a registerSiteSettings utility function that makes it easy to register all your settings in the root of your app.
Arguments
- Name
settings- Type
- Array<Schema>
- Description
An array of schemas that define your site settings. Note, the schema structure is the same as what you would use in a section.
Example
import {registerStorefrontSettingsSchema} from '@pack/react';
import footer from './footer';
export function registerSiteSettings() {
registerStorefrontSettingsSchema([footer]);
}
useSiteSettings()
The useSiteSettings hook allows you to access the structured data that you defined with your site settings schema. This hook must be used inside the <PackProvider /> component.
Usage
Here at Pack, we recommend creating a sections/ folder in the project where all your sections will live. This sections/ folder will export a registerSections utility function that makes it easy to register all your sections in the root of your app.
Example
import {ReactNode} from 'react';
import {Header} from './Header';
import {Footer} from './Footer';
import {useSiteSettings} from '@pack/react';
export function Layout({
children,
}: {
siteSettings?: Record<string, any>;
children: ReactNode;
}) {
const siteSettings = useSiteSettings();
return (
<>
<Header settings={siteSettings?.settings?.header} />
<main role="main" id="mainContent" className="flex-grow py-4">
{children}
</main>
<Footer settings={siteSettings?.settings?.footer} />
</>
);
}
usePlaybookCart()
Added in @pack/react 4.4.0. usePlaybookCart is an opt-in bridge that lets Playbook's collection-grid "Add to cart" button add lines through your storefront's own cart, so the cart UI updates and the drawer can open. Without a bridge, Playbook falls back to linking to the product page.
function usePlaybookCart(addToCart: PlaybookCartAdd | null | undefined): void
type PlaybookCartAdd = (input: PlaybookCartInput) => Promise<unknown> | unknown
interface PlaybookCartInput {
variantId: string // always a ProductVariant GID when your callback runs
quantity: number // positive integer
[key: string]: unknown // any extra fields Playbook sends are forwarded
}
- Opt-in. Nothing is registered unless you call the hook with a function. Pass
nullto disable it, for example when Playbook is not configured. - Variant IDs. Playbook sends a numeric variant ID. The hook converts it to a Storefront GID (
gid://shopify/ProductVariant/<id>) before calling your function, and rejects empty or non-variant IDs. - Resolve or throw. Resolve when the line was added. Throw (or reject) when it failed, and Playbook shows the shopper a failure state.
- Cleanup. The handler is registered on
window.playbookAddToCartand cleared on unmount, only if it is still the registered handler, so a route transition cannot remove a newer bridge.
Mount it inside your cart provider:
Example
import { usePlaybookCart } from '@pack/react';
import { useCart } from '~/hooks'; // your storefront's cart hook
export function PlaybookCartBridge() {
const { linesAdd } = useCart();
usePlaybookCart(async ({ variantId, quantity }) => {
await linesAdd([{ merchandiseId: variantId, quantity }]);
});
return null;
}
Some cart helpers resolve with userErrors instead of throwing. Check the
result and throw on failure so Playbook doesn't report a line as added when it
wasn't.