Building a Smart Vue 3 Cart Manager Composable 🛒

Alan Buenrostro
2025-05-27T17:08:08Z
Building a Smart Vue 3 Cart Manager Composable 🛒
Ever found yourself writing the same cart logic over and over across different components? I recently built a Vue 3 composable that centralizes all cart item interactions around one simple concept: the "focused item."
The Problem We're Solving
Think about how users interact with shopping carts. They don't just dump items in randomly. There's always a flow where they focus on one specific item, maybe adjust its quantity, then perform an action like adding it to cart or removing it entirely.
Traditional approaches scatter this logic across multiple components, making it hard to maintain and prone to bugs. What if we could capture this natural user behavior in a single, reusable composable?
The "Focused Item" Approach
The core insight is that cart interactions always revolve around one item at a time. Whether a user is viewing a product modal, adjusting quantities, or managing their cart, there's always one item that has their attention. This becomes our "focused item."
Here's how the composable works:
import { ref } from "vue"
function useCartManager() {
// The item currently receiving user attention
const currentFocusedItem = ref({});
// Focus an item from different data sources
const focusItem = function (options) {
if (!options.from) {
return console.error(`Function missing the from: property`)
}
let optionHandler = {
// When you have a single product object (like from product details)
"object": function (options) {
const itemToFocus = options.data.value;
if (!itemToFocus.quantity) {
itemToFocus.quantity = 0; // Ensure quantity exists
}
currentFocusedItem.value = itemToFocus;
},
// When you need to find an item in an array (like from search results)
"array": function (options) {
const itemToFocus = options.data.find((item) => {
return item.id === options.id
})
if (!itemToFocus.quantity) {
itemToFocus.quantity = 0;
}
currentFocusedItem.value = itemToFocus;
}
}
if (optionHandler.hasOwnProperty(options.from)) {
optionHandler[options.from](options)
} else {
return console.error(`${options.from} is an incorrect field`)
}
}
// Smart quantity adjustment with validation
const changeFocusedItemQuantity = function (number) {
const currentQuantity = currentFocusedItem.value.quantity;
const newQuantity = currentQuantity + number;
// Keep quantities reasonable (0-100)
if (newQuantity >= 0 && newQuantity <= 100) {
currentFocusedItem.value.quantity = newQuantity;
}
};
// Intelligent cart addition that handles duplicates
const addFocusedItemToCart = function (cart) {
const itemInCart = cart.find(item => {
return item.id === currentFocusedItem.value.id
})
if (itemInCart) {
// Item exists, just increase quantity
itemInCart.quantity += currentFocusedItem.value.quantity
} else {
// New item, add it to cart
cart.push(currentFocusedItem.value);
}
}
// Clean cart removal
const deleteCurrentFocusedItemFromCart = function (cart) {
const itemIndex = cart.findIndex((cartItem) => {
return cartItem.id === currentFocusedItem.value.id
})
cart.splice(itemIndex, 1);
}
return {
currentFocusedItem,
focusItem,
changeFocusedItemQuantity,
addFocusedItemToCart,
deleteCurrentFocusedItemFromCart
}
}
Real-World Usage
The beauty of this pattern becomes clear when you see how it simplifies component logic. Here's a typical product modal workflow:
const cartManager = useCartManager()
const { cart } = useCartStore()
// User clicks "Add to Cart" from product listing
function handleProductClick(productId, productsArray) {
// Focus the clicked product
cartManager.focusItem({
from: "array",
data: productsArray,
id: productId
})
// Set initial quantity
cartManager.changeFocusedItemQuantity(1)
// Show modal for quantity adjustment
showModal.value = true
}
// User confirms purchase
function confirmAddToCart() {
cartManager.addFocusedItemToCart(cart.value)
showModal.value = false
}
For cart editing, the pattern is equally clean:
// Focus on cart item for editing
function editCartItem(itemId) {
cartManager.focusItem({
from: "array",
data: cart.value,
id: itemId
})
}
// Simple quantity controls
const increaseQty = () => cartManager.changeFocusedItemQuantity(1)
const decreaseQty = () => cartManager.changeFocusedItemQuantity(-1)
const removeItem = () => cartManager.deleteCurrentFocusedItemFromCart(cart.value)
Why This Approach Works
This composable succeeds because it mirrors natural user behavior. Users don't think about complex cart algorithms. They focus on one item, decide what to do with it, then move on. The composable captures this mental model perfectly.
The flexible focusItem
method handles the reality that product data comes from various sources. Sometimes you have a single product object from an API, other times you're picking from a filtered array. The composable adapts to your data structure rather than forcing you to adapt to it.
Built-in validation prevents common edge cases like negative quantities or accidentally adding undefined items to carts. The error messages help during development, while the quantity limits prevent user confusion in production.
Key Benefits
The focused item pattern eliminates prop drilling by centralizing cart state management. Instead of passing cart functions down through multiple component layers, any component can access the cart manager and work with the currently focused item.
Testing becomes straightforward because each function has a single responsibility. You can test item focusing separately from quantity changes, and cart operations independently from item selection.
The composable scales naturally as your app grows. Need to add wishlist functionality? The same focusing pattern applies. Want to implement item comparisons? Focus multiple items and compare them. The pattern extends without breaking existing code.
Wrapping Up
This cart manager composable demonstrates how good Vue 3 composition functions should work. They capture natural user workflows, provide flexible APIs, and make complex interactions feel simple. The focused item concept proves that sometimes the best technical solutions are the ones that mirror how users already think about the problem.
The next time you're building cart functionality, consider whether your users are really managing multiple items simultaneously, or if they're focusing on one item at a time. The answer might lead you to simpler, more maintainable code that better serves your users' mental models.