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.
Install the SDK and set up your React application:
npm install @commercengine/storefront-sdk
# or
yarn add @commercengine/storefront-sdk
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'
});
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;
}
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;
}
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
};
}
// 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>
);
}
// 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>
);
}
// 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>
);
}
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;
Cart APIs are stateless and return the full updated cart - no need to manage complex state
Always provide both product_id and variant_id (can be null) when working with cart items
Use context providers to share SDK instances and authentication state across your application
Leverage TypeScript and the SDK’s type definitions for better development experience
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.