|
1 | 1 | "use client"; |
2 | 2 |
|
3 | | -import { useState, useTransition } from "react"; |
| 3 | +import { useState, useRef, useEffect } from "react"; |
4 | 4 | import Image from "next/image"; |
5 | 5 | import Link from "next/link"; |
6 | 6 | import { useRouter } from "next/navigation"; |
@@ -54,20 +54,42 @@ export function CartEditor({ initialCart }: { initialCart: Cart }) { |
54 | 54 | const router = useRouter(); |
55 | 55 | const [cart, setCart] = useState<Cart>(initialCart); |
56 | 56 | const [deleting, setDeleting] = useState(false); |
57 | | - const [, startTransition] = useTransition(); |
58 | 57 |
|
59 | | - const patch = async (changes: Partial<Cart>) => { |
| 58 | + // Debounced save: local state is the source of truth while editing. We |
| 59 | + // accumulate changed fields and PATCH them ~600ms after the last keystroke, |
| 60 | + // and we DON'T overwrite local state with the server echo — otherwise an |
| 61 | + // in-flight save for an earlier keystroke lands and reverts what you're |
| 62 | + // typing (text "getting replaced"). |
| 63 | + const pending = useRef<Partial<Cart>>({}); |
| 64 | + const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null); |
| 65 | + |
| 66 | + const flushSave = async () => { |
| 67 | + const changes = pending.current; |
| 68 | + pending.current = {}; |
| 69 | + if (Object.keys(changes).length === 0) return; |
| 70 | + try { |
| 71 | + await updateCart(initialCart.id, changes); |
| 72 | + } catch { |
| 73 | + toast.error("Couldn't save changes. Please try again."); |
| 74 | + } |
| 75 | + }; |
| 76 | + |
| 77 | + const patch = (changes: Partial<Cart>) => { |
60 | 78 | setCart((c) => ({ ...c, ...changes })); |
61 | | - startTransition(async () => { |
62 | | - try { |
63 | | - const updated = await updateCart(cart.id, changes); |
64 | | - setCart(updated); |
65 | | - } catch { |
66 | | - toast.error("Couldn't save changes. Please try again."); |
67 | | - } |
68 | | - }); |
| 79 | + pending.current = { ...pending.current, ...changes }; |
| 80 | + if (saveTimer.current) clearTimeout(saveTimer.current); |
| 81 | + saveTimer.current = setTimeout(flushSave, 600); |
69 | 82 | }; |
70 | 83 |
|
| 84 | + // Flush any pending edit when leaving the editor. |
| 85 | + useEffect(() => { |
| 86 | + return () => { |
| 87 | + if (saveTimer.current) clearTimeout(saveTimer.current); |
| 88 | + void flushSave(); |
| 89 | + }; |
| 90 | + // eslint-disable-next-line react-hooks/exhaustive-deps |
| 91 | + }, []); |
| 92 | + |
71 | 93 | const addProduct = async (draft: Omit<Product, "id">) => { |
72 | 94 | try { |
73 | 95 | const updated = await addProductToCart(cart.id, draft); |
|
0 commit comments