Migrate Cart to TanStack Store
Now let’s swap every component from the Context-based useCart to the TanStack Store. The UI stays exactly the same. We are only changing where the state lives.
Update CartLink
Replace the contents of src/components/cart-link.tsx with:
import { Link } from "@tanstack/react-router";
import { useStore } from "@tanstack/react-store";
import { ShoppingCart } from "lucide-react";
import { cartStore } from "@/store/cart-store";
import { Badge } from "@/components/ui/badge";
export function CartLink() {
const totalItems = useStore(cartStore, (state) =>
state.items.reduce((sum, item) => sum + item.quantity, 0),
);
return (
<Link
to="/cart"
className="relative text-muted-foreground hover:text-foreground [&.active]:text-foreground"
>
<ShoppingCart className="h-5 w-5" />
{totalItems > 0 && (
<Badge className="absolute -top-2 -right-3 h-5 min-w-5 justify-center px-1 text-xs">
{totalItems}
</Badge>
)}
</Link>
);
}
The useCart hook is replaced with useStore(cartStore, selector). The selector picks the exact slice of state this component needs. CartLink only re-renders when the total item count changes, not on every cart update.
Update the root layout
Replace the contents of src/routes/__root.tsx with:
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
import { CartLink } from "@/components/cart-link";
export const Route = createRootRoute({
component: RootLayout,
});
function RootLayout() {
return (
<div className="min-h-screen bg-background text-foreground">
<header className="border-b">
<nav className="mx-auto flex max-w-5xl items-center justify-between px-4 py-3">
<Link to="/" className="text-xl font-bold">
eStore
</Link>
<div className="flex items-center gap-4">
<Link
to="/"
className="text-sm text-muted-foreground hover:text-foreground [&.active]:text-foreground"
>
Products
</Link>
<CartLink />
</div>
</nav>
</header>
<main className="mx-auto max-w-5xl px-4 py-8">
<Outlet />
</main>
</div>
);
}
Two things changed:
- No
CartProviderwrapper. The store does not need a provider. The<CartProvider>import and tag are both gone. CartLinknow usesuseStoreinternally. It subscribes directly to the store instead of going through Context.
Update the product detail page
In src/routes/products.$productId.tsx, replace the context import:
// Before:
import { useCart } from "@/hooks/use-cart";
// After:
import { addToCart } from "@/store/cart-store";
Then remove the hook call. addToCart is now a plain function, not something returned by a hook:
// Before:
const { addToCart } = useCart();
// After: (just delete the line — addToCart is imported directly)
The button stays the same:
<Button className="mt-6" size="lg" onClick={() => addToCart(product)}>
Add to Cart
</Button>
Update the cart page
In src/routes/cart.tsx, replace the imports and hook:
// Before:
import { useCart } from "@/hooks/use-cart";
// After:
import { useStore } from "@tanstack/react-store";
import {
cartStore,
removeFromCart,
updateQuantity,
clearCart,
} from "@/store/cart-store";
Inside CartPage, replace the useCart() call:
// Before:
const { items, removeFromCart, updateQuantity, clearCart, totalPrice } =
useCart();
// After:
const items = useStore(cartStore, (state) => state.items);
const totalPrice = items.reduce(
(sum, item) => sum + item.product.price * item.quantity,
0,
);
The rest of the JSX stays exactly the same. The handlers (removeFromCart, updateQuantity, clearCart) are now imported directly instead of destructured from a hook.
The store’s updateQuantity function also removes an item if the requested quantity is 0 or less, so the state stays valid even if another part of the app calls it directly.
Delete the old context files
Since everything now uses the store, delete the old context, reducer, provider, and hook files:
rm -r src/context src/reducers src/providers src/hooks
The app looks and works exactly the same. What changed is the architecture. The state lives outside the React tree. Each component subscribes to the slice of state it needs, and the update functions are plain imports instead of values returned by a hook.
Checkpoint: Commit your progress.
git add .
git commit -m "shopping-cart-12: Migrate cart from React Context to TanStack Store"
git push