@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'
ExportDescription
PackProviderRoot provider for site settings, preview mode, and the Customizer connection.
PreviewProviderAlias of PackProvider, kept for existing storefronts.
registerSectionRegisters a React component as a section.
registerStorefrontSettingsSchemaRegisters site settings schemas for the Customizer.
setPackConsentRecords the visitor's consent with Pack, for consent tools other than Shopify's privacy banner.
RenderSectionsRenders all sections of a page.
RenderSectionRenders a single section, e.g. a nested section inside a blocks field.
useSectionsReturns the rendered section elements instead of rendering them.
useSiteSettingsReturns the current site settings.
useLocaleReturns the current { language, country } locale from PackProvider, or null.
usePackContextReturns the raw Pack context (isPreview, customizerMeta, siteSettings, liveContent, contentEnvironment, locale).
useOverlayScriptInjects the Customizer overlay script in preview mode. Called for you by PackProvider.
usePlaybookCartOpt-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.

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.


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

    name is a unique key for this section. It must match the section schema's key, 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.

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 id or clientId. 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.

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 null to 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.playbookAddToCart and 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;
  }

Was this page helpful?