Cart Persistence
How cart data is saved and restored across sessions.
Overview
Cart persistence ensures customers don't lose their cart when they:
- Refresh the page
- Close and reopen the browser
- Navigate between pages
- Return to your site later
How It Works
Auto-Save
Every cart change is automatically saved:
User adds item
↓
Cart state updates
↓
State serialized to JSON
↓
Saved to storage
↓
User refreshes page
↓
State loaded from storage
↓
Cart restoredConfiguration
<CartProvider
stripe={STRIPE_KEY}
currency="USD"
shouldPersist={true} // Enable persistence (default)
persistKey="my-shopping-cart" // Storage key (optional)
>
<App />
</CartProvider>Storage Adapters
LocalStorage (Default)
Saves to browser's localStorage:
<CartProvider
shouldPersist={true}
// Uses localStorage by default
>
<App />
</CartProvider>Pros:
- Persists across sessions
- Available in all browsers
- ~5-10MB storage limit
Cons:
- Not available in incognito mode
- Synchronous (can block)
- Not shared across tabs automatically
SessionStorage
Clears when browser closes:
import { createSessionStorage } from 'use-shopping-cart/core'
;<CartProvider shouldPersist={true} storage={createSessionStorage()}>
<App />
</CartProvider>Use when:
- Cart should clear on browser close
- Temporary shopping sessions
- Multi-tab isolation needed
Memory Storage
No persistence (testing/SSR):
import { createMemoryStorage } from 'use-shopping-cart/core'
;<CartProvider shouldPersist={true} storage={createMemoryStorage()}>
<App />
</CartProvider>Use when:
- Server-side rendering
- Testing
- No persistence needed
No Storage
Disable persistence completely:
<CartProvider shouldPersist={false}>
<App />
</CartProvider>Custom Storage
Interface
interface Storage {
getItem(key: string): string | null
setItem(key: string, value: string): void
removeItem(key: string): void
}Example: IndexedDB
const indexedDBStorage = {
async getItem(key) {
const db = await openDB()
return db.get('cart', key)
},
async setItem(key, value) {
const db = await openDB()
await db.put('cart', value, key)
},
async removeItem(key) {
const db = await openDB()
await db.delete('cart', key)
}
}
<CartProvider storage={indexedDBStorage} />Example: Server Sync
const serverSyncStorage = {
getItem(key) {
// Load from localStorage as fallback
return localStorage.getItem(key)
},
setItem(key, value) {
// Save locally
localStorage.setItem(key, value)
// Sync to server
fetch('/api/save-cart', {
method: 'POST',
body: JSON.stringify({ cart: value })
})
},
removeItem(key) {
localStorage.removeItem(key)
fetch('/api/clear-cart', { method: 'POST' })
}
}
<CartProvider storage={serverSyncStorage} />Storage Key
Customize the localStorage key name for multiple carts or to avoid conflicts:
// Main cart - stores in localStorage under 'main-cart'
<CartProvider persistKey="main-cart">
<MainShop />
</CartProvider>
// Gift cart - stores in localStorage under 'gift-cart'
<CartProvider persistKey="gift-cart">
<GiftShop />
</CartProvider>Note: The persistKey is used directly as the localStorage key name. There's no prefix or transformation applied.
What Gets Stored
Only cart-specific data is persisted to localStorage:
{
cartDetails: { /* all cart items */ },
cartCount: number,
totalPrice: number,
formattedTotalPrice: string,
currency: string,
language: string
}Configuration settings (stripe key, mode, etc.) are NOT stored - only cart state and formatting context.
Data Format
Serialized State
The cart data is stored as JSON in localStorage:
{
"cartCount": 2,
"totalPrice": 1200,
"formattedTotalPrice": "$12.00",
"currency": "USD",
"language": "en-US",
"cartDetails": {
"banana_001": {
"id": "banana_001",
"name": "Bananas",
"price": 400,
"quantity": 2,
"value": 800
},
"apple_001": {
"id": "apple_001",
"name": "Apples",
"price": 400,
"quantity": 1,
"value": 400
}
}
}Cross-Tab Sync
By default, changes in one tab don't automatically sync to other tabs. You can enable cross-tab sync:
import { useEffect } from 'react'
import { useShoppingCart } from 'use-shopping-cart'
function CrossTabSync() {
const { loadCart } = useShoppingCart()
useEffect(() => {
// Listen for storage changes from other tabs
const handleStorage = (e) => {
// Check if it's our cart key
if (e.key === 'use-shopping-cart' && e.newValue) {
const cart = JSON.parse(e.newValue)
loadCart(cart.cartDetails)
}
}
window.addEventListener('storage', handleStorage)
return () => window.removeEventListener('storage', handleStorage)
}, [])
return null
}Note: The storage event only fires in OTHER tabs, not the current tab. If using a custom persistKey, make sure to check for that key instead of 'use-shopping-cart'.
Manual Control
Save Manually
const { cartDetails } = useShoppingCart()
const handleSave = () => {
localStorage.setItem('backup-cart', JSON.stringify(cartDetails))
}Load Manually
const { loadCart } = useShoppingCart()
const handleRestore = () => {
const backup = localStorage.getItem('backup-cart')
if (backup) {
loadCart(JSON.parse(backup))
}
}Clear Storage
const { clearCart } = useShoppingCart()
const handleClearEverything = () => {
clearCart() // Clears cart state
localStorage.removeItem('use-shopping-cart') // Clears storage
}Best Practices
1. Version Your Data
const cart = {
version: 1,
data: cartDetails
}
localStorage.setItem(key, JSON.stringify(cart))2. Handle Corrupted Data
const customStorage = {
getItem(key) {
try {
const value = localStorage.getItem(key)
return value ? JSON.parse(value) : null
} catch (error) {
console.error('Corrupted cart data, clearing:', error)
localStorage.removeItem(key)
return null
}
}
}3. Expire Old Carts
const EXPIRY_DAYS = 30
const timestampedStorage = {
setItem(key, value) {
const data = {
value,
timestamp: Date.now()
}
localStorage.setItem(key, JSON.stringify(data))
},
getItem(key) {
const item = localStorage.getItem(key)
if (!item) return null
const { value, timestamp } = JSON.parse(item)
const age = Date.now() - timestamp
const maxAge = EXPIRY_DAYS * 24 * 60 * 60 * 1000
if (age > maxAge) {
localStorage.removeItem(key)
return null
}
return value
}
}4. Compress Large Carts
import { compress, decompress } from 'lz-string'
const compressedStorage = {
setItem(key, value) {
const compressed = compress(value)
localStorage.setItem(key, compressed)
},
getItem(key) {
const compressed = localStorage.getItem(key)
return compressed ? decompress(compressed) : null
}
}Troubleshooting
Cart Not Persisting
Check:
shouldPersist={true}is set- Storage is available (not incognito)
- Storage quota not exceeded
- No localStorage errors in console
Cart Clearing on Refresh
Check:
- Using localStorage, not sessionStorage
- Not calling
clearCart()on mount - persistKey is consistent
- No browser extensions clearing storage
Data Loss
Implement backup strategy:
function BackupCart() {
const { cartDetails } = useShoppingCart()
useEffect(() => {
// Backup to server every 30 seconds
const interval = setInterval(() => {
if (Object.keys(cartDetails).length > 0) {
fetch('/api/backup-cart', {
method: 'POST',
body: JSON.stringify({ cart: cartDetails })
})
}
}, 30000)
return () => clearInterval(interval)
}, [cartDetails])
}See Also
- ShoppingCart Class - Core class
- shouldPersist - Enable/disable persistence
- persistKey - Custom storage key
- loadCart - Load cart manually
