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: setState takes an updater function directly (like useState, but for external state)
  • Update functions are plain functions: not hooks. You can call addToCart from 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