Introduction to TanStack Store
Our cart works, but React Context has a limitation: when the context value changes, every mounted component that calls useContext (or our useCart hook) re-renders, even if it only uses one small piece of the value. For example, when you add an item to the cart, the CartLink component re-renders to update the badge count.
This does not matter much in a small app. But in larger apps with many context consumers, it can become a performance problem. TanStack Store solves this with selector-based subscriptions: each component picks the exact slice of state it needs, and only re-renders when that slice changes.
Install TanStack Store
pnpm add @tanstack/store @tanstack/react-store
Two packages: @tanstack/store is the core (framework-agnostic), and @tanstack/react-store provides the useStore hook for React.
Create the cart store
Create src/store/cart-store.ts:
import { createStore } from "@tanstack/store";
import type { Product } from "@/api/types";
export type CartItem = {
product: Product;
quantity: number;
};
type CartState = {
items: CartItem[];
};
export const cartStore = createStore<CartState>({ items: [] });
export function addToCart(product: Product) {
cartStore.setState((state) => {
const existing = state.items.find((item) => item.product.id === product.id);
if (existing) {
return {
items: state.items.map((item) =>
item.product.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item,
),
};
}
return {
items: [...state.items, { product, quantity: 1 }],
};
});
}
export function removeFromCart(productId: number) {
cartStore.setState((state) => ({
items: state.items.filter((item) => item.product.id !== productId),
}));
}
export function updateQuantity(productId: number, quantity: number) {
cartStore.setState((state) => ({
items:
quantity <= 0
? state.items.filter((item) => item.product.id !== productId)
: state.items.map((item) =>
item.product.id === productId ? { ...item, quantity } : item,
),
}));
}
export function clearCart() {
cartStore.setState(() => ({ items: [] }));
}
Compare this to the Context version:
- No Provider needed: the store lives outside the React tree as a plain module
- No reducer or dispatch:
setStatetakes an updater function directly (likeuseState, but for external state) - Update functions are plain functions: not hooks. You can call
addToCartfrom anywhere, not just inside a React component
The state update logic is the same as our reducer, just expressed differently.
Checkpoint: Commit your progress.
git add .
git commit -m "shopping-cart-11: Install TanStack Store and create cart store"
git push