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
The SDK provides React components and hooks for seamless integration with your Hydrogen application.
GloodProvider
A React context provider that automatically subscribes to Shopify Analytics events and manages pixel tracking across all registered Glood apps.
Signature
function GloodProvider({
client,
children
}: GloodProviderProps): React.ReactElement
Props
| Prop | Type | Required | Description |
client | GloodClient | Yes | Glood client instance created with createGlood() |
children | React.ReactNode | Yes | Child components to wrap |
Basic Usage
import { Analytics } from '@shopify/hydrogen';
import { GloodProvider, createGlood } from '@glood/hydrogen';
import { recommendations, search, wishlist } from '@glood/hydrogen';
const glood = createGlood({
apiKey: process.env.GLOOD_API_KEY!,
myShopifyDomain: 'your-store.myshopify.com',
})
.use(recommendations())
.use(search())
.use(wishlist());
export default function App() {
return (
<Analytics>
<GloodProvider client={glood} loaderData={data}>
<Outlet />
</GloodProvider>
</Analytics>
);
}
How It Works
The GloodProvider component:
- Creates React Context - Provides the Glood client to all child components
- Subscribes to Analytics - Uses Hydrogen’s
useAnalytics() hook to receive events
- Distributes Events - Routes events to interested apps based on their subscriptions
- Checks Consent - Verifies customer privacy permissions before sending pixels
- Handles Errors - Provides comprehensive error handling and debug logging
Event Subscription Flow
Error Handling
The provider includes comprehensive error handling:
// 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:
// 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]);
Context Integration
The provider creates a React Context to share the client:
const GloodContext = createContext<GloodClient | null>(null);
export function GloodProvider({ client, children }) {
// ... analytics setup
return (
<GloodContext.Provider value={client}>
{children}
</GloodContext.Provider>
);
}
useGloodAnalytics
A React hook that provides access to the Glood client from context.
Signature
function useGloodAnalytics(): GloodClient | null
Returns
| Type | Description |
GloodClient | null | The Glood client instance or null if used outside provider |
Usage
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>
);
}
Custom Integrations
Use the hook for custom integrations with Glood apps:
import { useGloodAnalytics } from '@glood/hydrogen';
function CustomRecommendations() {
const glood = useGloodAnalytics();
const [recommendations, setRecommendations] = useState([]);
useEffect(() => {
if (!glood) return;
const recommendationsApp = glood.getApp('recommendations');
if (!recommendationsApp) return;
// Custom API call to recommendations endpoint
fetch(`${recommendationsApp.endpoint}/api/recommendations`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${glood.config.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
shopDomain: glood.config.myShopifyDomain,
// ... other parameters
}),
})
.then(response => response.json())
.then(data => setRecommendations(data.recommendations))
.catch(error => console.error('Recommendations error:', error));
}, [glood]);
return (
<div>
{recommendations.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
Conditional Rendering
Use the hook for conditional rendering based on Glood availability:
import { useGloodAnalytics } from '@glood/hydrogen';
function EnhancedSearchBox() {
const glood = useGloodAnalytics();
const hasSearchApp = glood?.getApp('search');
if (hasSearchApp) {
return <GloodSearchBox />;
}
return <StandardSearchBox />;
}
Provider Placement
Correct Placement
The GloodProvider must be placed correctly in your component tree:
// ✅ 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
// ❌ 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:
const glood = createGlood({
apiKey: process.env.GLOOD_API_KEY!,
myShopifyDomain: 'your-store.myshopify.com',
debug: process.env.NODE_ENV === 'development',
});
<GloodProvider client={glood} loaderData={data}>
<App />
</GloodProvider>
Debug logs include:
[Glood Debug] Setting up event subscriptions for apps: ['recommendations', 'search', 'wishlist']
[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', 'search_submitted', ...]
[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', 'search', 'wishlist']
[Glood Debug] Processing page_viewed event for recommendations: { url: '/products/shirt', ... }
[Glood Debug] Consent granted for recommendations, processing event
Memoization
The provider uses React’s useMemo to prevent unnecessary re-renders:
const clientConfig = useMemo(() => ({
debug: client.debug,
enabledApps: client.getEnabledApps(),
appsKeys: Array.from(client.apps.keys())
}), [client.debug, client.apps]);
Event Deduplication
Events are subscribed to only once per event type, regardless of how many apps are interested:
// 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:
useEffect(() => {
const timer = setTimeout(() => {
// Setup analytics subscriptions
}, 0);
return () => clearTimeout(timer);
}, [clientConfig, analytics]);
Error Scenarios
Missing Analytics
// Hydrogen analytics not available
if (!analytics?.customerPrivacy) {
if (debug) {
console.log('[Glood Debug] Skipping analytics setup: analytics not available');
}
return;
}
Network Errors
// 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
// 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:
// ✅ 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:
// ✅ 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:
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:
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