use-shopping-cart logouse-shopping-cart
Hooks

useOptimisticCart()

New in v4.0.0 - Optimistic version of useShoppingCart() that provides instant UI feedback using React 19's useOptimistic hook.

Overview

useOptimisticCart() wraps useShoppingCart() and adds optimistic updates. When users perform cart operations (add, remove, update quantity), they see instant feedback in the UI while the actual cart operation completes in the background.

Benefits

Instant UI feedback - No waiting for cart updates
Better perceived performance - Feels faster even if localStorage sync takes time
Automatic rollback - If an operation fails, the UI automatically reverts
Drop-in replacement - Same API as useShoppingCart()

Requirements

  • React 19.0.0 or higher
  • use-shopping-cart v4.0.0 or higher

Basic Usage

import { useOptimisticCart } from 'use-shopping-cart'

function ProductCard({ product }) {
  const { addItem, isOptimistic } = useOptimisticCart()

  return (
    <button onClick={() => addItem(product)} disabled={isOptimistic}>
      {isOptimistic ? 'Adding...' : 'Add to Cart'}
    </button>
  )
}

API

useOptimisticCart() returns the same properties and methods as useShoppingCart(), plus:

Additional Properties

isOptimistic

  • Type: boolean
  • Description: true when the cart is showing optimistic updates that haven't been confirmed yet
const { isOptimistic } = useOptimisticCart()

{
  isOptimistic && <span>Syncing changes...</span>
}

Item-Level Optimistic Indicator

Each cart item has an _optimistic property when it's being optimistically updated:

const { cartDetails } = useOptimisticCart()

Object.values(cartDetails).map((item) => (
  <div key={item.id}>
    {item.name} - Qty: {item.quantity}
    {item._optimistic && <span> (updating...)</span>}
  </div>
))

Examples

Cart with Loading States

function ShoppingCart() {
  const {
    cartDetails,
    cartCount,
    formattedTotalPrice,
    clearCart,
    isOptimistic
  } = useOptimisticCart()

  return (
    <div className="cart">
      <h2>Your Cart ({cartCount} items)</h2>

      {isOptimistic && (
        <div className="syncing-indicator">🔄 Syncing changes...</div>
      )}

      {Object.values(cartDetails).map((item) => (
        <CartItem key={item.id} item={item} />
      ))}

      <div className="cart-total">
        Total:{' '}
        <span className={isOptimistic ? 'updating' : ''}>
          {formattedTotalPrice}
        </span>
      </div>

      <button onClick={clearCart} disabled={isOptimistic}>
        Clear Cart
      </button>
    </div>
  )
}

Cart Item with Quantity Controls

function CartItem({ item }) {
  const { incrementItem, decrementItem, removeItem, isOptimistic } =
    useOptimisticCart()

  return (
    <div className="cart-item">
      <h4>{item.name}</h4>

      <div className="quantity-controls">
        <button
          onClick={() => decrementItem(item.id)}
          disabled={isOptimistic || item.quantity === 1}
        >
          -
        </button>

        <span className={item._optimistic ? 'updating' : ''}>
          {item.quantity}
        </span>

        <button onClick={() => incrementItem(item.id)} disabled={isOptimistic}>
          +
        </button>
      </div>

      <button onClick={() => removeItem(item.id)} disabled={isOptimistic}>
        Remove
      </button>
    </div>
  )
}

Advanced: Per-Item Loading

function ProductCard({ product }) {
  const { addItem, cartDetails } = useOptimisticCart()

  const itemInCart = cartDetails[product.id]
  const isThisItemOptimistic = itemInCart?._optimistic === true

  return (
    <div className="product">
      <h3>{product.name}</h3>
      <p>${product.price / 100}</p>

      {itemInCart && (
        <div className="in-cart-badge">
          In cart: {itemInCart.quantity}
          {isThisItemOptimistic && ' (updating...)'}
        </div>
      )}

      <button onClick={() => addItem(product)} disabled={isThisItemOptimistic}>
        {isThisItemOptimistic ? 'Adding...' : 'Add to Cart'}
      </button>
    </div>
  )
}

Migration from useShoppingCart()

Migrating is simple - just replace useShoppingCart with useOptimisticCart:

- import { useShoppingCart } from 'use-shopping-cart'
+ import { useOptimisticCart } from 'use-shopping-cart'

function MyComponent() {
-  const { addItem, cartCount } = useShoppingCart()
+  const { addItem, cartCount, isOptimistic } = useOptimisticCart()

  return (
    <button onClick={() => addItem(product)}>
-      Add to Cart
+      {isOptimistic ? 'Adding...' : 'Add to Cart'}
    </button>
  )
}

When to Use

Use useOptimisticCart() when:

  • You want instant UI feedback for cart operations
  • You're dealing with slow network or localStorage operations
  • You want to improve perceived performance

Stick with useShoppingCart() when:

  • You prefer simpler code without optimistic updates
  • You don't need loading states
  • You're migrating from older versions and want minimal changes

TypeScript

import { useOptimisticCart } from 'use-shopping-cart'

function ProductCard({ product }: { product: Product }) {
  const { addItem, isOptimistic } = useOptimisticCart()

  return (
    <button onClick={() => addItem(product)} disabled={isOptimistic}>
      {isOptimistic ? 'Adding...' : 'Add to Cart'}
    </button>
  )
}

Interactive Demo

Cart Display

Total Items: 0

Cart is empty. Add items to see them here!

Bananas product photo

Bananas

Yummy yellow fruit

$4.00

Try Optimistic Updates:

💡 What's happening?

  • Cart count increases instantly (optimistic update)
  • Shows "Confirming..." while syncing in background
  • Try the error button - UI automatically rolls back on failure
  • Uses React 19's useOptimistic hook

Note: 2.5 second delay added to this demo to showcase the confirmation state

Try adding items and watch the instant UI feedback! Notice how the interface updates immediately, and the "updating..." indicator shows while the cart syncs in the background.

See Also

Shopping Cart

Your cart is empty

Try the interactive demos on the docs pages!