Persist Cart to localStorage
Right now, if you refresh the page, your cart is empty. The store starts with { items: [] } every time. Let’s fix that by saving cart state to localStorage.
Add storage helpers
Open src/store/cart-store.ts and add these two functions near the top of the file, before the store creation:
const STORAGE_KEY = "shopping-cart";
function loadCart(): CartItem[] {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
}
function saveCart(items: CartItem[]) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
}
loadCart reads from localStorage and parses the JSON. The try/catch handles corrupt data. If parsing fails, it returns an empty array instead of crashing. saveCart writes the current items to localStorage.
This is the same pattern you used in the KudoBoard chapter (loadBoards/saveBoards). The difference is where we call these functions. In the KudoBoard, we used useEffect to sync state because it lived inside React. Here, the store lives outside React, so we will call saveCart directly inside each mutation function.
Initialize the store from localStorage
Change the store creation to load saved items:
// Before:
export const cartStore = createStore<CartState>({ items: [] });
// After:
export const cartStore = createStore<CartState>({ items: loadCart() });
Now the store starts with whatever was previously saved. On the first visit (nothing in localStorage), loadCart() returns []. This is the same default as before.
Save inside each mutation
Update every mutation function to call saveCart before returning the new state. Here is addToCart:
export function addToCart(product: Product) {
cartStore.setState((state) => {
const existing = state.items.find((item) => item.product.id === product.id);
if (existing) {
const items = state.items.map((item) =>
item.product.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item,
);
saveCart(items);
return { items };
}
const items = [...state.items, { product, quantity: 1 }];
saveCart(items);
return { items };
});
}
The change is that we compute the new items array, save it to localStorage, and then return it as the new state. Apply the same pattern to the other three functions:
export function removeFromCart(productId: number) {
cartStore.setState((state) => {
const items = state.items.filter((item) => item.product.id !== productId);
saveCart(items);
return { items };
});
}
export function updateQuantity(productId: number, quantity: number) {
cartStore.setState((state) => {
const items =
quantity <= 0
? state.items.filter((item) => item.product.id !== productId)
: state.items.map((item) =>
item.product.id === productId ? { ...item, quantity } : item,
);
saveCart(items);
return { items };
});
}
export function clearCart() {
cartStore.setState(() => {
const items: CartItem[] = [];
saveCart(items);
return { items };
});
}
Every function that changes the cart now persists the change.
The updated store
Here is the full src/store/cart-store.ts with persistence:
import { createStore } from "@tanstack/store";
import type { Product } from "@/api/types";
export type CartItem = {
product: Product;
quantity: number;
};
type CartState = {
items: CartItem[];
};
const STORAGE_KEY = "shopping-cart";
function loadCart(): CartItem[] {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
}
function saveCart(items: CartItem[]) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
}
export const cartStore = createStore<CartState>({ items: loadCart() });
export function addToCart(product: Product) {
cartStore.setState((state) => {
const existing = state.items.find((item) => item.product.id === product.id);
if (existing) {
const items = state.items.map((item) =>
item.product.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item,
);
saveCart(items);
return { items };
}
const items = [...state.items, { product, quantity: 1 }];
saveCart(items);
return { items };
});
}
export function removeFromCart(productId: number) {
cartStore.setState((state) => {
const items = state.items.filter((item) => item.product.id !== productId);
saveCart(items);
return { items };
});
}
export function updateQuantity(productId: number, quantity: number) {
cartStore.setState((state) => {
const items =
quantity <= 0
? state.items.filter((item) => item.product.id !== productId)
: state.items.map((item) =>
item.product.id === productId ? { ...item, quantity } : item,
);
saveCart(items);
return { items };
});
}
export function clearCart() {
cartStore.setState(() => {
const items: CartItem[] = [];
saveCart(items);
return { items };
});
}
Try it out
Add a few items to your cart, then refresh the page. Your cart items should still be there. Then clear the cart and refresh again. The cart should be empty.
Checkpoint: Commit your progress.
git add .
git commit -m "shopping-cart-13: Persist cart to localStorage"
git push