React Integration
Integrate the useLoyalty Widget into React, Next.js, and other React-based frameworks
This guide covers integrating the useLoyalty Widget into React applications using the official @useloyalty/sdk package.
Installation
npm install @useloyalty/sdk
# or
pnpm add @useloyalty/sdk
# or
yarn add @useloyalty/sdkQuick Start
1. Create Server-Side Auth Endpoint
The SDK provides a helper to generate the HMAC signature:
// app/api/useloyalty/auth/route.ts (Next.js App Router)
import { NextResponse } from 'next/server';
import { generateWidgetAuth } from '@useloyalty/sdk';
export async function POST(request: Request) {
const { userId } = await request.json();
if (!userId) {
return NextResponse.json({ error: 'User ID required' }, { status: 400 });
}
const auth = generateWidgetAuth(
process.env.USELOYALTY_PUBLIC_KEY!,
process.env.USELOYALTY_PRIVATE_KEY!,
userId
);
return NextResponse.json(auth);
}2. Add Widget to Your App
Option A: Using the Hook (Recommended)
// app/layout.tsx
'use client';
import { useLoyaltyWidget } from '@useloyalty/sdk/react';
import { useAuth } from '@/hooks/useAuth';
function UseLoyaltyWidgetLoader() {
const { user } = useAuth();
useLoyaltyWidget({
getAuth: async () => {
const res = await fetch('/api/useloyalty/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: user?.id }),
});
return res.json();
},
member: {
email: user?.email,
name: user?.name,
},
theme: {
primaryColor: '#6366f1',
},
});
return null;
}
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<UseLoyaltyWidgetLoader />
</body>
</html>
);
}Option B: Using Provider Pattern
// app/providers.tsx
'use client';
import { UseLoyaltyProvider } from '@useloyalty/sdk/react';
import { useAuth } from '@/hooks/useAuth';
export function Providers({ children }) {
const { user } = useAuth();
return (
<UseLoyaltyProvider
getAuth={async () => {
const res = await fetch('/api/useloyalty/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: user?.id }),
});
return res.json();
}}
theme={{ primaryColor: '#6366f1' }}
>
{children}
</UseLoyaltyProvider>
);
}Then use anywhere in your app:
// components/RewardsButton.tsx
'use client';
import { useLoyalty } from '@useloyalty/sdk/react';
export function RewardsButton() {
const { isReady, open } = useLoyalty();
return (
<button onClick={open} disabled={!isReady}>
🎁 View Rewards
</button>
);
}Server-Side SDK
Generate Widget Auth
import { generateWidgetAuth, generateWidgetAuthWithMember } from '@useloyalty/sdk';
// Basic auth
const auth = generateWidgetAuth(
process.env.USELOYALTY_PUBLIC_KEY!,
process.env.USELOYALTY_PRIVATE_KEY!,
userId
);
// With member data
const authWithMember = generateWidgetAuthWithMember(
process.env.USELOYALTY_PUBLIC_KEY!,
process.env.USELOYALTY_PRIVATE_KEY!,
userId,
{
email: user.email,
name: user.name,
avatarUrl: user.avatar,
}
);API Client
Use the typed client for server-side API calls:
import { UseLoyaltyClient } from '@useloyalty/sdk';
const useLoyalty = new UseLoyaltyClient({
publicKey: process.env.USELOYALTY_PUBLIC_KEY!,
privateKey: process.env.USELOYALTY_PRIVATE_KEY!,
});
// Award points
await useLoyalty.members.awardPoints(userId, {
amount: 100,
description: 'Welcome bonus',
});
// Track events
await useLoyalty.events.track(userId, {
event: 'purchase',
properties: { amount: 99.99 },
});
// Complete a quest
await useLoyalty.quests.complete(userId, 'quest_123');
// Award a badge
await useLoyalty.badges.award(userId, 'badge_early_adopter');
// Redeem promo code
await useLoyalty.promoCodes.redeem(userId, { code: 'WELCOME50' });Custom Trigger Button
Use displayMode: 'custom' to hide the floating button and trigger from your own UI:
'use client';
import { useLoyaltyWidget } from '@useloyalty/sdk/react';
export function App() {
const { isReady, open, toggle } = useLoyaltyWidget({
getAuth: async () => {
const res = await fetch('/api/useloyalty/auth', { method: 'POST' });
return res.json();
},
theme: {
launcher: {
displayMode: 'custom', // Hide floating button
},
},
});
return (
<nav>
<button onClick={open} disabled={!isReady}>
🎁 Rewards
</button>
</nav>
);
}With UseLoyaltyTrigger Component
import { UseLoyaltyProvider, UseLoyaltyTrigger } from '@useloyalty/sdk/react';
function App() {
return (
<UseLoyaltyProvider
getAuth={fetchAuth}
theme={{ launcher: { displayMode: 'custom' } }}
>
<nav>
<UseLoyaltyTrigger className="nav-button">
🎁 Rewards
</UseLoyaltyTrigger>
</nav>
</UseLoyaltyProvider>
);
}React Hooks Reference
useLoyaltyWidget
Main hook for widget management:
const {
isLoaded, // Script loaded
isReady, // Widget initialized
isOpen, // Widget drawer open
error, // Any error
open, // Open widget
close, // Close widget
toggle, // Toggle widget
init, // Manual init
destroy, // Cleanup
} = useLoyaltyWidget({
getAuth, // Required: async function returning WidgetAuth
member, // Optional: member data
theme, // Optional: widget theme
language, // Optional: widget language
scriptUrl, // Optional: custom script URL
apiUrl, // Optional: custom API URL
onReady, // Optional: ready callback
onError, // Optional: error callback
onWelcomeBonus, // Optional: welcome bonus callback
autoInit, // Optional: auto-init (default: true)
});useLoyalty
Context hook (use within UseLoyaltyProvider):
const { isReady, open, close, toggle } = useLoyalty();useLoyaltyAuth
Helper hook for fetching auth:
const { auth, isLoading, error, refetch } = useLoyaltyAuth({
endpoint: '/api/useloyalty/auth',
userId: user?.id ?? null,
member: { email: user?.email },
});TypeScript Types
The SDK exports all necessary types:
import type {
// Widget
WidgetAuth,
WidgetTheme,
WidgetConfig,
WidgetLanguage,
LauncherConfig,
LauncherDisplayMode,
LauncherVariant,
LauncherIcon,
MemberData,
// API
Member,
AwardPointsInput,
TrackEventInput,
Badge,
Quest,
} from '@useloyalty/sdk';
import type {
UseLoyaltyWidgetOptions,
UseLoyaltyWidgetReturn,
} from '@useloyalty/sdk/react';Next.js App Router Example
Complete example with authentication:
// app/api/useloyalty/auth/route.ts
import { NextResponse } from 'next/server';
import { generateWidgetAuthWithMember } from '@useloyalty/sdk';
import { getServerSession } from 'next-auth';
export async function POST() {
const session = await getServerSession();
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const auth = generateWidgetAuthWithMember(
process.env.USELOYALTY_PUBLIC_KEY!,
process.env.USELOYALTY_PRIVATE_KEY!,
session.user.id,
{
email: session.user.email,
name: session.user.name,
avatarUrl: session.user.image,
}
);
return NextResponse.json(auth);
}// app/layout.tsx
import { UseLoyaltyWidgetClient } from '@/components/UseLoyaltyWidgetClient';
import { getServerSession } from 'next-auth';
export default async function RootLayout({ children }) {
const session = await getServerSession();
return (
<html>
<body>
{children}
{session?.user && <UseLoyaltyWidgetClient />}
</body>
</html>
);
}// components/UseLoyaltyWidgetClient.tsx
'use client';
import { useLoyaltyWidget } from '@useloyalty/sdk/react';
export function UseLoyaltyWidgetClient() {
useLoyaltyWidget({
getAuth: async () => {
const res = await fetch('/api/useloyalty/auth', { method: 'POST' });
if (!res.ok) throw new Error('Auth failed');
return res.json();
},
theme: {
primaryColor: '#6366f1',
launcher: {
variant: 'icon-text',
text: 'Rewards',
pulse: true,
},
},
onReady: () => console.log('Widget ready'),
});
return null;
}Troubleshooting
Widget Not Appearing
- Check that the user is authenticated
- Verify API route returns valid auth data
- Check browser console for errors
Hydration Errors
Ensure you use 'use client' directive:
'use client';
import { useLoyaltyWidget } from '@useloyalty/sdk/react';Multiple Widget Instances
The hook handles this automatically with a ref check. Only one widget instance is created.
Cleanup on Route Changes
The hook automatically cleans up on unmount. For manual cleanup:
const { destroy } = useLoyaltyWidget({ ... });
// Call when needed
destroy();