> ## Documentation Index
> Fetch the complete documentation index at: https://docs.glood.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# React Components

> Complete reference for GloodProvider and the useGloodAnalytics, useGloodInit, and useRecommendations hooks

# React Components

The SDK provides React components and hooks for seamless integration with your Hydrogen application.

## Lifecycle

On page load the SDK's components and hooks fire in a fixed sequence. `GloodProvider` bootstraps the session first (v3 init), and `useRecommendations` waits for that to settle before fetching sections — so recommendations are always attributed to the init-issued visitor.

```mermaid theme={null}
sequenceDiagram
    participant Page as Page load
    participant GP as GloodProvider
    participant API as Glood API (v3)
    participant UR as useRecommendations
    participant UI as Your component

    Page->>GP: mount (inside Analytics.Provider)
    GP->>API: POST /v3/headless/init
    Note over GP: also subscribes to Shopify analytics events
    API-->>GP: InitResponse (user_id, visit_id, config, token)
    Note over GP: useGloodInit() gets data; useGloodInitReady() becomes true
    GP-->>UR: init ready
    UR->>API: POST /v3/headless/sections (carries init user_id)
    API-->>UR: sections + experience
    UR-->>UI: { sections, loading: false }
    UI->>API: trackRender / View / Click → POST /v3/headless/events
```

**Sequence of hooks:**

1. **`GloodProvider` mounts** — subscribes to Shopify analytics and, for a v3 client, calls `/v3/headless/init` once. (v2 clients skip init.)
2. **`useGloodInit()`** — returns the init response once it resolves (`null` until then, and always `null` for v2). Also readable synchronously via `client.getInitData()`.
3. **`useGloodInitReady()`** — flips to `true` when init settles (or immediately for v2 / no client). This is the gate the next step waits on.
4. **`useRecommendations(params)`** — holds in `loading` until `useGloodInitReady()` is `true`, then fetches `/v3/headless/sections`. The request carries the init-issued `user_id`.
5. **`useGloodAnalytics()` + `trackRender/View/Click/AddToCart`** — fire attribution events (through the recommendations app) to `/v3/headless/events` as the visitor interacts.

<Note>
  For a **v2** client there is no init call: `useGloodInitReady()` is `true` immediately, so `useRecommendations` fetches without waiting. Events route to `/api/storefront/event` instead of `/v3/headless/events`.
</Note>

## GloodProvider

A React context provider that automatically subscribes to Shopify Analytics events, manages pixel tracking for the recommendations app, and — for v3 clients — fires the headless session init on mount.

### Signature

```typescript theme={null}
function GloodProvider({
  client,
  loaderData,
  children
}: GloodProviderProps): React.ReactElement
```

### Props

| Prop         | Type              | Required | Description                                        |
| ------------ | ----------------- | -------- | -------------------------------------------------- |
| `client`     | `GloodClient`     | Yes      | Glood client instance created with `createGlood()` |
| `loaderData` | `unknown`         | No       | Route loader data forwarded to enabled apps        |
| `children`   | `React.ReactNode` | Yes      | Child components to wrap                           |

### Basic Usage

```tsx theme={null}
import { Analytics } from '@shopify/hydrogen';
import { GloodProvider, createGlood, recommendations } from '@glood/hydrogen';

const glood = createGlood({
  apiKey: process.env.GLOOD_API_KEY!,
  myShopifyDomain: 'your-store.myshopify.com',
  version: 3, // 2 | 3, default 3
}).use(recommendations());

export default function App() {
  return (
    <Analytics>
      <GloodProvider client={glood} loaderData={data}>
        <Outlet />
      </GloodProvider>
    </Analytics>
  );
}
```

### How It Works

The `GloodProvider` component:

1. **Creates React Context** - Provides the Glood client to all child components
2. **Fires Headless Init** - On mount, a v3 client calls `POST /api/storefront/v3/headless/init` once to bootstrap the session. The response is exposed via [`useGloodInit()`](#usegloodinit). For a v2 client this is a no-op.
3. **Subscribes to Analytics** - Uses Hydrogen's `useAnalytics()` hook to receive events
4. **Distributes Events** - Routes events to the recommendations app based on its subscriptions
5. **Checks Consent** - Verifies customer privacy permissions before sending pixels
6. **Handles Errors** - Provides comprehensive error handling and debug logging

<Note>
  The headless init runs exactly once per provider mount and only when the client is configured for API version 3 (the default). It replays a persisted visitor id, creates a per-visit id, and returns the shop config, storefront token, and visitor history. Init failures are logged but never block rendering.
</Note>

### Event Subscription Flow

```mermaid theme={null}
graph TD
    A[Shopify Analytics] --> B[GloodProvider]
    B --> C[Event Distribution]
    C --> D[Consent Check]
    D --> E[Pixel Transmission]
    E --> F[Glood Endpoints]
```

### Error Handling

The provider includes comprehensive error handling:

```tsx theme={null}
// Invalid client handling
<GloodProvider client={null}>
  <App />
</GloodProvider>
// Logs: "[Glood] GloodProvider: client is required"
// Renders children without Glood functionality

// Debug mode error logging
const glood = createGlood({
  apiKey: process.env.GLOOD_API_KEY!,
  myShopifyDomain: 'your-store.myshopify.com',
  debug: true, // Enable detailed error logging
});

<GloodProvider client={glood} loaderData={data}>
  <App />
</GloodProvider>
```

### Server-Side Rendering

The provider automatically handles SSR — analytics subscriptions only run in the browser:

```tsx theme={null}
// Only runs on client-side
useEffect(() => {
  if (typeof window === 'undefined' || !analytics?.customerPrivacy) {
    if (debug) {
      console.log('[Glood Debug] Skipping analytics setup: not in browser');
    }
    return;
  }
  // Setup analytics subscriptions
}, [clientConfig, analytics]);
```

## useGloodAnalytics

A React hook that provides access to the Glood client from context.

### Signature

```typescript theme={null}
function useGloodAnalytics(): GloodClient | null
```

### Returns

| Type                  | Description                                                  |
| --------------------- | ------------------------------------------------------------ |
| `GloodClient \| null` | The Glood client instance or `null` if used outside provider |

### Usage

```tsx theme={null}
import { useGloodAnalytics } from '@glood/hydrogen';

function MyComponent() {
  const glood = useGloodAnalytics();

  if (!glood) {
    // Component used outside GloodProvider
    return <div>Glood not available</div>;
  }

  // Access client properties
  const isDebugMode = glood.debug;
  const enabledApps = glood.getEnabledApps();

  return (
    <div>
      <p>Debug mode: {isDebugMode ? 'On' : 'Off'}</p>
      <p>Enabled apps: {enabledApps.map(app => app.name).join(', ')}</p>
    </div>
  );
}
```

## useGloodInit

A React hook that returns the `/v3/headless/init` response fired by `GloodProvider` on mount.

### Signature

```typescript theme={null}
function useGloodInit(): InitResponse | null
```

### Returns

| Type                   | Description                                                                                                 |
| ---------------------- | ----------------------------------------------------------------------------------------------------------- |
| `InitResponse \| null` | The headless init response. `null` until init resolves, and always `null` for v2 clients (init is v3-only). |

The `InitResponse` carries the server-issued session identity and shop context: the echoed `user_id`, the per-visit `visit_id`, `visitor` history (browsed / cart / purchased), the shop `config` (currency, money format, storefront GraphQL version, integrations, analytics enabled), and the storefront `token`.

### Usage

```tsx theme={null}
import { useGloodInit } from '@glood/hydrogen';

function CurrencyBadge() {
  const init = useGloodInit();

  // null until init resolves, and always null for v2 clients
  if (!init) return null;

  return <span>Currency: {init.config?.shop?.currency}</span>;
}
```

<Note>
  Outside React, read the same value synchronously with
  [`client.getInitData()`](/for-developers/glood-hydrogen-sdk/api-reference/client#getinitdata).
</Note>

## useGloodInitReady

A React hook that reports whether the page-load headless init has **settled** — resolved, failed, or skipped (v2 / no client). `useRecommendations` waits on this internally so the init call lands before the first sections request; you can use it to gate your own init-dependent calls.

### Signature

```typescript theme={null}
function useGloodInitReady(): boolean
```

### Returns

| Type      | Description                                                                                                                       |
| --------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `boolean` | `false` while a v3 init is in flight; `true` once it settles (or immediately for v2 / no client, which have no init to wait for). |

### Usage

```tsx theme={null}
import { useGloodInitReady, useGloodAnalytics } from '@glood/hydrogen';

function useRecommendationsAfterInit(params) {
  const ready = useGloodInitReady();
  const client = useGloodAnalytics();

  useEffect(() => {
    if (!ready) return; // hold until init has run
    client?.getApp('recommendations')?.getRecommendations(params);
  }, [ready]);
}
```

## useRecommendations

A React hook that fetches recommendation sections client-side (in the browser). Prefer this for client-side rendering; use `fetchRecommendationSections(options, params)` in SSR route loaders instead.

Must be used within a `GloodProvider` whose client has the recommendations app registered.

<Note>
  **Ordering:** for a v3 client the hook waits for the page-load
  `/v3/headless/init` call to settle before fetching sections (it stays in the
  `loading` state until then). This guarantees init runs first and the sections
  request carries the init-issued visitor id. For v2 clients there is no init,
  so sections fetch immediately.
</Note>

Full signature, parameters, and return fields are documented in the [Recommendations API](/for-developers/glood-hydrogen-sdk/api-reference/recommendations-api#userecommendations).

### Usage

```tsx theme={null}
import { useRecommendations } from '@glood/hydrogen';

function ProductRecommendations({ productId }: { productId: string }) {
  const { sections, loading, error, trackClick } = useRecommendations({
    pageType: 'product_page',
    productId,
  });

  if (loading) return <div>Loading…</div>;
  if (error) return null;

  return (
    <>
      {sections.map(section => (
        <section key={section.id}>
          <h2>{section.title}</h2>
          {section.products.map(product => (
            <a
              key={product.product_id}
              href={`/products/${product.handle}`}
              onClick={() => trackClick({ section, product })}
            >
              {product.title}
            </a>
          ))}
        </section>
      ))}
    </>
  );
}
```

## Provider Placement

### Correct Placement

The `GloodProvider` must be placed correctly in your component tree:

```tsx theme={null}
// ✅ Correct - Inside Analytics, wrapping application routes
import { Analytics } from '@shopify/hydrogen';

export default function App() {
  return (
    <html>
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Analytics>
          <GloodProvider client={glood} loaderData={data}>
            <Layout>
              <Outlet />
            </Layout>
          </GloodProvider>
        </Analytics>
        <Scripts />
      </body>
    </html>
  );
}
```

### Incorrect Placement

```tsx theme={null}
// ❌ Incorrect - Outside Analytics component
export default function App() {
  return (
    <GloodProvider client={glood} loaderData={data}>
      <Analytics>
        <Outlet />
      </Analytics>
    </GloodProvider>
  );
}

// ❌ Incorrect - Inside individual routes
export default function ProductPage() {
  return (
    <GloodProvider client={glood} loaderData={data}>
      <ProductDetails />
    </GloodProvider>
  );
}
```

## Debug Logging

Enable debug mode to see detailed component behavior:

```tsx theme={null}
const glood = createGlood({
  apiKey: process.env.GLOOD_API_KEY!,
  myShopifyDomain: 'your-store.myshopify.com',
  debug: process.env.NODE_ENV === 'development',
}).use(recommendations());

<GloodProvider client={glood} loaderData={data}>
  <App />
</GloodProvider>
```

Debug logs include:

```
[Glood Debug] Setting up event subscriptions for apps: ['recommendations']
[Glood Debug] Set analytics instance on recommendations app
[Glood Debug] App recommendations subscribes to events: ['page_viewed', 'product_viewed', ...]
[Glood Debug] All unique event types: ['page_viewed', 'product_viewed', ...]
[Glood Debug] Setting up centralized subscription for page_viewed
[Glood Debug] Received page_viewed event, distributing to interested apps
[Glood Debug] Apps interested in page_viewed: ['recommendations']
[Glood Debug] Processing page_viewed event for recommendations: { url: '/products/shirt', ... }
[Glood Debug] Consent granted for recommendations, processing event
[Glood Debug] Headless init complete: { user_id: '…', visit_id: '…', config: { … } }
```

## Performance Considerations

### Memoization

The provider uses React's `useMemo` to prevent unnecessary re-renders:

```tsx theme={null}
const clientConfig = useMemo(() => ({
  debug: client.debug,
  enabledApps: client.getEnabledApps(),
  appsKeys: Array.from(client.apps.keys())
}), [client]);
```

### Event Deduplication

Events are subscribed to only once per event type, regardless of how many apps are interested:

```tsx theme={null}
// Subscribe once per event type
allEventTypes.forEach((eventType: EventType) => {
  subscribe(eventType, (eventData: any) => {
    // Distribute to all interested apps
    const interestedApps = enabledAppsWithPixel.filter(app =>
      app.subscribedEvents.includes(eventType)
    );

    interestedApps.forEach(app => {
      app.handleEvent(eventType, eventData, client);
    });
  });
});
```

### Lazy Loading

Analytics setup is deferred to prevent blocking:

```tsx theme={null}
useEffect(() => {
  const timer = setTimeout(() => {
    // Setup analytics subscriptions
  }, 0);

  return () => clearTimeout(timer);
}, [clientConfig, analytics]);
```

## Error Scenarios

### Missing Analytics

```tsx theme={null}
// Hydrogen analytics not available
if (!analytics?.customerPrivacy) {
  if (debug) {
    console.log('[Glood Debug] Skipping analytics setup: analytics not available');
  }
  return;
}
```

### Network Errors

```tsx theme={null}
// Pixel transmission errors are handled gracefully
try {
  app.handleEvent(eventType, eventData, client);
} catch (error) {
  console.error('[Glood] Error processing event:', error);
  if (debug) {
    console.error('[Glood Debug] Event data:', eventData);
  }
}
```

### Consent Denial

```tsx theme={null}
// Events are not processed if consent is not granted
if (checkConsent(app.pixel.consent, canTrack, analytics)) {
  app.handleEvent(eventType, eventData, client);
} else {
  if (debug) {
    console.log(`[Glood Debug] Consent not granted for ${app.name}, skipping event`);
  }
}
```

## Best Practices

### 1. Single Provider Instance

Use only one `GloodProvider` at the root of your application:

```tsx theme={null}
// ✅ Good - Single provider at root
<GloodProvider client={glood} loaderData={data}>
  <App />
</GloodProvider>

// ❌ Bad - Multiple providers
<GloodProvider client={glood1}>
  <Header />
</GloodProvider>
<GloodProvider client={glood2}>
  <Main />
</GloodProvider>
```

### 2. Client Stability

Create the client outside of the component to prevent recreating:

```tsx theme={null}
// ✅ Good - Client created once
const glood = createGlood(config).use(recommendations());

export default function App() {
  return (
    <GloodProvider client={glood} loaderData={data}>
      <Outlet />
    </GloodProvider>
  );
}

// ❌ Bad - Client recreated on every render
export default function App() {
  const glood = createGlood(config).use(recommendations());

  return (
    <GloodProvider client={glood} loaderData={data}>
      <Outlet />
    </GloodProvider>
  );
}
```

### 3. Conditional Hook Usage

Always check for null when using the hook:

```tsx theme={null}
function MyComponent() {
  const glood = useGloodAnalytics();

  // ✅ Good - Check for null
  if (!glood) {
    return <FallbackComponent />;
  }

  // Use glood safely
  const apps = glood.getEnabledApps();
}
```

### 4. Error Boundaries

Wrap the provider in error boundaries for production:

```tsx theme={null}
import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error }) {
  return (
    <div>
      <h2>Glood Error</h2>
      <pre>{error.message}</pre>
    </div>
  );
}

export default function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <Analytics>
        <GloodProvider client={glood} loaderData={data}>
          <Outlet />
        </GloodProvider>
      </Analytics>
    </ErrorBoundary>
  );
}
```

## See Also

* [Client API](/for-developers/glood-hydrogen-sdk/api-reference/client) - createGlood() and GloodClient
* [App Modules](/for-developers/glood-hydrogen-sdk/api-reference/app-modules) - App configuration and usage
* [Event System](/for-developers/glood-hydrogen-sdk/api-reference/event-system) - Event tracking and pixel transmission
* [Examples](/for-developers/glood-hydrogen-sdk/examples/recommendations) - Complete working recommendations page
