Getting Started
Core Features
Framework Integration
Advanced Usage
React Integration
Complete guide to integrating the SDK with React applications
The SDK integrates seamlessly with React applications, providing type-safe hooks, context providers, and component patterns for e-commerce functionality.
Installation and Setup
Install the SDK and set up your React application:
npm install @commercengine/storefront-sdk
# or
yarn add @commercengine/storefront-sdk
Basic Configuration
Create a shared SDK instance:
// lib/storefront.ts
import { StorefrontSDK, Environment, BrowserTokenStorage } from '@commercengine/storefront-sdk';
export const storefront = new StorefrontSDK({
storeId: process.env.REACT_APP_STORE_ID!,
environment: process.env.NODE_ENV === 'production' ? Environment.Production : Environment.Staging,
apiKey: process.env.REACT_APP_API_KEY!,
tokenStorage: new BrowserTokenStorage('myapp_'),
debug: process.env.NODE_ENV === 'development'
});
Context Providers
Commerce Context
Create a context to share the SDK instance across your app:
// contexts/CommerceContext.tsx
import React, { createContext, useContext, ReactNode } from 'react';
import { StorefrontSDK } from '@commercengine/storefront-sdk';
import { storefront } from '../lib/storefront';
interface CommerceContextType {
client: StorefrontSDK;
}
const CommerceContext = createContext<CommerceContextType | undefined>(undefined);
export function CommerceProvider({ children }: { children: ReactNode }) {
return (
<CommerceContext.Provider value={{ client: storefront }}>
{children}
</CommerceContext.Provider>
);
}
export function useCommerce() {
const context = useContext(CommerceContext);
if (context === undefined) {
throw new Error('useCommerce must be used within a CommerceProvider');
}
return context;
}
Authentication Context
Manage authentication state across your application:
// contexts/AuthContext.tsx
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { useCommerce } from './CommerceContext';
import type { UserInfo } from '@commercengine/storefront-sdk';
interface AuthContextType {
user: UserInfo | null;
isLoading: boolean;
isLoggedIn: boolean;
login: (email: string) => Promise<boolean>;
logout: () => Promise<void>;
refreshUser: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const { client } = useCommerce();
const [user, setUser] = useState<UserInfo | null>(null);
const [isLoading, setIsLoading] = useState(true);
const isLoggedIn = user !== null;
const refreshUser = async () => {
try {
const userInfo = await client.getUserInfo();
setUser(userInfo);
} catch (error) {
setUser(null);
}
};
const login = async (email: string): Promise<boolean> => {
setIsLoading(true);
try {
const result = await client.auth.loginWithEmail({
email,
register_if_not_exists: true
});
if (result.success) {
await refreshUser();
return true;
}
return false;
} catch (error) {
return false;
} finally {
setIsLoading(false);
}
};
const logout = async () => {
setIsLoading(true);
try {
await client.auth.logoutUser();
await client.clearTokens();
setUser(null);
} catch (error) {
// Force clear even if API call fails
await client.clearTokens();
setUser(null);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
refreshUser().finally(() => setIsLoading(false));
}, []);
return (
<AuthContext.Provider value={{
user,
isLoading,
isLoggedIn,
login,
logout,
refreshUser
}}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
Custom Hooks
useCart Hook
Manage shopping cart state with stateless cart operations:
// hooks/useCart.ts
import { useState, useEffect } from 'react';
import { useCommerce } from '../contexts/CommerceContext';
import { useAuth } from '../contexts/AuthContext';
import type { Cart } from '@commercengine/storefront-sdk';
interface CartItem {
product_id: string;
variant_id: string | null; // Always required, can be null
quantity: number;
}
interface UseCartReturn {
cart: Cart | null;
loading: boolean;
error: string | null;
addItem: (item: CartItem) => Promise<boolean>;
updateItem: (item: CartItem) => Promise<boolean>;
removeItem: (productId: string, variantId?: string | null) => Promise<boolean>;
clearCart: () => Promise<boolean>;
refresh: () => Promise<void>;
itemCount: number;
}
export function useCart(): UseCartReturn {
const { client } = useCommerce();
const { user, isLoggedIn } = useAuth();
const [cart, setCart] = useState<Cart | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchCart = async () => {
if (!isLoggedIn || !user) {
setCart(null);
return;
}
setLoading(true);
setError(null);
try {
const result = await client.cart.retrieveCartUsingUserId(user.sub);
if (result.success) {
setCart(result.data);
} else {
setError(result.error.message);
setCart(null);
}
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
setCart(null);
} finally {
setLoading(false);
}
};
const addItem = async (item: CartItem): Promise<boolean> => {
setLoading(true);
setError(null);
try {
const result = await client.cart.addDeleteCartItem({
product_id: item.product_id,
variant_id: item.variant_id,
quantity: item.quantity
});
if (result.success) {
// Cart APIs are stateless and return the full updated cart
setCart(result.data);
return true;
} else {
setError(result.error.message);
return false;
}
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
return false;
} finally {
setLoading(false);
}
};
const updateItem = async (item: CartItem): Promise<boolean> => {
// Same as addItem since cart APIs handle updates by product_id + variant_id
return addItem(item);
};
const removeItem = async (productId: string, variantId: string | null = null): Promise<boolean> => {
return addItem({
product_id: productId,
variant_id: variantId,
quantity: 0 // Setting quantity to 0 removes the item
});
};
const clearCart = async (): Promise<boolean> => {
if (!cart?.id) return false;
setLoading(true);
setError(null);
try {
const result = await client.cart.deleteCart(cart.id);
if (result.success) {
setCart(null);
return true;
} else {
setError(result.error.message);
return false;
}
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
return false;
} finally {
setLoading(false);
}
};
// Calculate total items in cart
const itemCount = cart?.items?.reduce((total, item) => total + item.quantity, 0) || 0;
useEffect(() => {
fetchCart();
}, [isLoggedIn, user]);
return {
cart,
loading,
error,
addItem,
updateItem,
removeItem,
clearCart,
refresh: fetchCart,
itemCount
};
}
Component Examples
Add to Cart Example
// components/AddToCartButton.tsx
import React, { useState } from 'react';
import { useCart } from '../hooks/useCart';
interface AddToCartButtonProps {
productId: string;
variantId?: string | null;
quantity?: number;
}
export function AddToCartButton({
productId,
variantId = null,
quantity = 1
}: AddToCartButtonProps) {
const { addItem, loading } = useCart();
const [isAdding, setIsAdding] = useState(false);
const handleAddToCart = async () => {
setIsAdding(true);
const success = await addItem({
product_id: productId,
variant_id: variantId, // Always required, can be null
quantity
});
if (success) {
// Optional: Show success message
console.log('Item added to cart successfully');
}
setIsAdding(false);
};
return (
<button
onClick={handleAddToCart}
disabled={loading || isAdding}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 disabled:opacity-50"
>
{isAdding ? 'Adding...' : 'Add to Cart'}
</button>
);
}
Authentication Component
// components/AuthButton.tsx
import React, { useState } from 'react';
import { useAuth } from '../contexts/AuthContext';
export function AuthButton() {
const { isLoggedIn, user, login, logout, isLoading } = useAuth();
const [email, setEmail] = useState('');
const [showLogin, setShowLogin] = useState(false);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
const success = await login(email);
if (success) {
setShowLogin(false);
setEmail('');
}
};
if (isLoading) {
return <div>Loading...</div>;
}
if (isLoggedIn) {
return (
<div className="flex items-center gap-4">
<span>Welcome, {user?.email}</span>
<button
onClick={logout}
className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600"
>
Logout
</button>
</div>
);
}
return (
<div>
{showLogin ? (
<form onSubmit={handleLogin} className="flex gap-2">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
className="border rounded px-3 py-2"
required
/>
<button
type="submit"
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Login
</button>
<button
type="button"
onClick={() => setShowLogin(false)}
className="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600"
>
Cancel
</button>
</form>
) : (
<button
onClick={() => setShowLogin(true)}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Login
</button>
)}
</div>
);
}
Cart Component
// components/Cart.tsx
import React from 'react';
import { useCart } from '../hooks/useCart';
export function Cart() {
const { cart, loading, updateItem, removeItem, itemCount } = useCart();
if (loading) return <div>Loading cart...</div>;
if (!cart || !cart.items?.length) return <div>Your cart is empty</div>;
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold">Shopping Cart</h2>
<span className="text-sm text-gray-600">{itemCount} items</span>
</div>
{cart.items.map(item => (
<div key={`${item.product_id}-${item.variant_id || 'default'}`} className="flex items-center justify-between border-b pb-4">
<div>
<h3 className="font-semibold">{item.product_name}</h3>
{item.variant_name && (
<p className="text-sm text-gray-500">{item.variant_name}</p>
)}
<p className="text-gray-600">${item.selling_price}</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => updateItem({
product_id: item.product_id,
variant_id: item.variant_id,
quantity: Math.max(0, item.quantity - 1)
})}
className="bg-gray-200 px-2 py-1 rounded hover:bg-gray-300"
disabled={loading}
>
-
</button>
<span className="mx-2">{item.quantity}</span>
<button
onClick={() => updateItem({
product_id: item.product_id,
variant_id: item.variant_id,
quantity: item.quantity + 1
})}
className="bg-gray-200 px-2 py-1 rounded hover:bg-gray-300"
disabled={loading}
>
+
</button>
<button
onClick={() => removeItem(item.product_id, item.variant_id)}
className="bg-red-500 text-white px-3 py-1 rounded ml-4 hover:bg-red-600"
disabled={loading}
>
Remove
</button>
</div>
</div>
))}
<div className="pt-4 space-y-2">
<div className="flex justify-between">
<span>Subtotal:</span>
<span>${cart.sub_total}</span>
</div>
{cart.tax_amount > 0 && (
<div className="flex justify-between">
<span>Tax:</span>
<span>${cart.tax_amount}</span>
</div>
)}
{cart.shipping_amount > 0 && (
<div className="flex justify-between">
<span>Shipping:</span>
<span>${cart.shipping_amount}</span>
</div>
)}
<div className="flex justify-between text-xl font-bold border-t pt-2">
<span>Total:</span>
<span>${cart.grand_total}</span>
</div>
</div>
</div>
);
}
App Integration
Wrap your app with the providers:
// App.tsx
import React from 'react';
import { CommerceProvider } from './contexts/CommerceContext';
import { AuthProvider } from './contexts/AuthContext';
import { AddToCartButton } from './components/AddToCartButton';
import { AuthButton } from './components/AuthButton';
import { Cart } from './components/Cart';
function App() {
return (
<CommerceProvider>
<AuthProvider>
<div className="container mx-auto p-4">
<header className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold">My Store</h1>
<AuthButton />
</header>
<main className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2">
{/* Your product catalog components */}
<div className="space-y-4">
<h2 className="text-xl font-semibold">Products</h2>
{/* Example product cards with add to cart */}
<div className="border rounded-lg p-4">
<h3 className="font-semibold">Sample Product 1</h3>
<p className="text-gray-600">$29.99</p>
<AddToCartButton
productId="product-123"
variantId={null}
quantity={1}
/>
</div>
<div className="border rounded-lg p-4">
<h3 className="font-semibold">Sample Product 2</h3>
<p className="text-gray-600">$39.99</p>
<AddToCartButton
productId="product-456"
variantId="variant-789"
quantity={1}
/>
</div>
</div>
</div>
<div>
<Cart />
</div>
</main>
</div>
</AuthProvider>
</CommerceProvider>
);
}
export default App;
Best Practices
Stateless Cart Operations
Cart APIs are stateless and return the full updated cart - no need to manage complex state
Product & Variant IDs
Always provide both product_id and variant_id (can be null) when working with cart items
Context Providers
Use context providers to share SDK instances and authentication state across your application
Type Safety
Leverage TypeScript and the SDK’s type definitions for better development experience
Cross-References
- Token Management: Token Management Guide for authentication patterns
- Error Handling: Error Handling Guide for comprehensive error management
- API Reference: API Reference for endpoint documentation
The SDK’s React integration provides type-safe hooks and context patterns that make it easy to build robust e-commerce applications with proper state management and error handling.