77 removeDiscountCode ,
88 updateCartLine ,
99} from '~/utils/shop.functions'
10- import type { CartDetail } from '~/utils/shopify-queries'
10+ import type { CartDetail , CartLineDetail } from '~/utils/shopify-queries'
1111
1212/**
1313 * Shared React Query key for the current user's cart.
@@ -72,12 +72,37 @@ export function useCart() {
7272 }
7373}
7474
75+ /**
76+ * Snapshot of the product/variant from the PDP, passed through to
77+ * onMutate so a full optimistic cart line can be rendered instantly.
78+ */
79+ type AddToCartLineSnapshot = {
80+ productTitle : string
81+ productHandle : string
82+ variantTitle : string
83+ price : { amount : string ; currencyCode : string }
84+ image : {
85+ url : string
86+ altText ?: string | null
87+ width ?: number | null
88+ height ?: number | null
89+ } | null
90+ selectedOptions : Array < { name : string ; value : string } >
91+ }
92+
93+ type AddToCartInput = {
94+ variantId : string
95+ quantity ?: number
96+ /** Product snapshot for optimistic line rendering. */
97+ line ?: AddToCartLineSnapshot
98+ }
99+
75100export function useAddToCart ( ) {
76101 const qc = useQueryClient ( )
77102
78103 return useMutation ( {
79104 mutationKey : CART_MUTATION_KEY ,
80- mutationFn : ( input : { variantId : string ; quantity ?: number } ) =>
105+ mutationFn : ( input : AddToCartInput ) =>
81106 addToCart ( {
82107 data : { variantId : input . variantId , quantity : input . quantity ?? 1 } ,
83108 } ) ,
@@ -86,12 +111,64 @@ export function useAddToCart() {
86111 const quantity = input . quantity ?? 1
87112 await qc . cancelQueries ( { queryKey : CART_QUERY_KEY } )
88113 const previous = qc . getQueryData < CartDetail | null > ( CART_QUERY_KEY )
89- if ( previous ) {
114+
115+ if ( previous && input . line ) {
116+ const snap = input . line
117+
118+ // Does this variant already have a line in the cart?
119+ const existingIdx = previous . lines . nodes . findIndex (
120+ ( l ) => l . merchandise . id === input . variantId ,
121+ )
122+
123+ let nextLines : CartDetail [ 'lines' ] [ 'nodes' ]
124+ if ( existingIdx >= 0 ) {
125+ nextLines = previous . lines . nodes . map ( ( l , i ) =>
126+ i === existingIdx
127+ ? { ...l , quantity : l . quantity + quantity }
128+ : l ,
129+ )
130+ } else {
131+ const lineTotal = String ( Number ( snap . price . amount ) * quantity )
132+ nextLines = [
133+ ...previous . lines . nodes ,
134+ {
135+ id : `optimistic-${ Date . now ( ) } ` ,
136+ quantity,
137+ merchandise : {
138+ id : input . variantId ,
139+ title : snap . variantTitle ,
140+ availableForSale : true ,
141+ selectedOptions : snap . selectedOptions ,
142+ price : snap . price ,
143+ image : snap . image ,
144+ product : {
145+ handle : snap . productHandle ,
146+ title : snap . productTitle ,
147+ } ,
148+ } ,
149+ cost : {
150+ totalAmount : {
151+ amount : lineTotal ,
152+ currencyCode : snap . price . currencyCode ,
153+ } ,
154+ } ,
155+ } as CartLineDetail ,
156+ ]
157+ }
158+
159+ qc . setQueryData < CartDetail | null > ( CART_QUERY_KEY , {
160+ ...previous ,
161+ totalQuantity : nextLines . reduce ( ( s , l ) => s + l . quantity , 0 ) ,
162+ lines : { ...previous . lines , nodes : nextLines } ,
163+ } )
164+ } else if ( previous ) {
165+ // No snapshot — fall back to just bumping the count
90166 qc . setQueryData < CartDetail | null > ( CART_QUERY_KEY , {
91167 ...previous ,
92168 totalQuantity : ( previous . totalQuantity ?? 0 ) + quantity ,
93169 } )
94170 }
171+
95172 return { previous }
96173 } ,
97174
@@ -100,10 +177,8 @@ export function useAddToCart() {
100177 qc . setQueryData ( CART_QUERY_KEY , ctx . previous )
101178 } ,
102179
103- // Add-to-cart needs onSuccess to populate the new line item in the
104- // cache — onMutate can only bump totalQuantity since it doesn't have
105- // the full product data. Unlike remove/update, rapid-fire adds are
106- // rare, and the worst case is a brief badge-count fluctuation.
180+ // Reconcile: replace the optimistic line (temporary ID, approximate
181+ // totals) with the real server response.
107182 onSuccess : ( cart ) => {
108183 qc . setQueryData ( CART_QUERY_KEY , cart )
109184 } ,
0 commit comments