Wishlist
Server-synced wishlist with optimistic UI updates, Zustand state management, and authentication-gated access
Wishlist
Authenticated users can save products to a wishlist that persists server-side. The client-side Zustand store provides optimistic updates with automatic rollback on API failure.
Architecture
Wishlist Store
The store (lib/store/wishlist.ts) uses Zustand with localStorage persistence:
interface WishlistStore {
items: WishlistItem[]
isLoading: boolean
addItem: (productId: string) => Promise<void>
removeItem: (productId: string) => Promise<void>
isInWishlist: (productId: string) => boolean
clearWishlist: () => void
fetchWishlist: () => Promise<void>
totalItems: () => number
}WishlistItem
Each wishlist item includes full product data for rendering without additional fetches:
interface WishlistItem {
id: string // WishlistItem.id from database
productId: string // Product.id
name: string
slug: string
price: number
compareAtPrice?: number | null
image: string
heatLevel: string
sku: string
inventory: number
isActive: boolean
addedAt: Date
}Optimistic Updates
Adding Items
- A temporary item with
id: temp-{productId}is immediately added to the store - A POST request is sent to
/api/wishlist - On success, the temporary item is replaced with the real server data
- On failure, the temporary item is removed (rollback)
Removing Items
- The item is immediately removed from the store
- A DELETE request is sent to
/api/wishlist - On failure, the original items are restored (rollback)
Authentication
The wishlist requires authentication:
- The
ProductCardcomponent checksuseSession()before toggling wishlist state - Unauthenticated users clicking the heart icon are redirected to sign-in
- The
fetchWishlist()function silently clears the store on 401/403 responses without logging errors
const handleWishlistToggle = () => {
if (!session) {
router.push('/auth/signin?callbackUrl=/products')
return
}
// toggle logic...
}Wishlist Page
The /wishlist page displays all saved products in a grid layout with:
- Product images, names, and prices
- Heat level badges
- Add-to-cart buttons
- Remove from wishlist action
The wishlist store uses partialize to only persist items to localStorage, providing instant loading on page refresh. The server-side state is fetched on mount via fetchWishlist() to ensure consistency.
How is this guide?
Last updated on