@@ -26,25 +26,31 @@ export const CART_QUERY_KEY = ['shopify', 'cart'] as const
2626const CART_MUTATION_KEY = [ 'shopify' , 'cart' , 'mutate' ] as const
2727
2828/**
29- * Only invalidate (refetch from server) when no other cart mutations are
30- * in flight. This prevents a settled mutation's refetch from overwriting
31- * another mutation's optimistic state with stale server data.
32- *
33- * Each `onMutate` reads the *current* cache (which may already reflect
34- * earlier optimistic writes) and layers its own change on top. When the
35- * last mutation settles, the refetch reconciles everything with the
36- * server's final truth.
29+ * Explicit in-flight counter. We don't rely on `queryClient.isMutating()`
30+ * because its exact semantics at `onSettled` time (does it still count the
31+ * current mutation?) vary across React Query versions and are under-documented.
32+ * A module-level counter is unambiguous: increment in onMutate, decrement in
33+ * onSettled, invalidate when the count hits zero.
34+ */
35+ let cartMutationsInFlight = 0
36+
37+ function trackMutationStart ( ) {
38+ cartMutationsInFlight ++
39+ }
40+
41+ /**
42+ * Call from every cart mutation's `onSettled`. Decrements the in-flight
43+ * counter, and when the last mutation settles, triggers a single background
44+ * refetch to reconcile all accumulated optimistic changes with server truth.
3745 *
3846 * Returns the invalidation promise so the mutation stays in `isPending`
39- * until the background refetch completes (per TkDodo's recommendation) .
47+ * until the refetch completes.
4048 *
4149 * @see https://tkdodo.eu/blog/concurrent-optimistic-updates-in-react-query
4250 */
4351function settleWhenIdle ( qc : ReturnType < typeof useQueryClient > ) {
44- // isMutating counts mutations that haven't settled yet. At the time
45- // onSettled fires, the *current* mutation is still counted, so
46- // 1 means "I'm the last one in flight."
47- if ( qc . isMutating ( { mutationKey : CART_MUTATION_KEY } ) === 1 ) {
52+ cartMutationsInFlight = Math . max ( 0 , cartMutationsInFlight - 1 )
53+ if ( cartMutationsInFlight === 0 ) {
4854 return qc . invalidateQueries ( { queryKey : CART_QUERY_KEY } )
4955 }
5056}
@@ -108,6 +114,7 @@ export function useAddToCart() {
108114 } ) ,
109115
110116 onMutate : async ( input ) => {
117+ trackMutationStart ( )
111118 const quantity = input . quantity ?? 1
112119 await qc . cancelQueries ( { queryKey : CART_QUERY_KEY } )
113120 const previous = qc . getQueryData < CartDetail | null > ( CART_QUERY_KEY )
@@ -128,7 +135,6 @@ export function useAddToCart() {
128135 } else {
129136 const lineTotal = String ( Number ( snap . price . amount ) * quantity )
130137 nextLines = [
131- ...previous . lines . nodes ,
132138 {
133139 id : `optimistic-${ Date . now ( ) } ` ,
134140 quantity,
@@ -151,6 +157,7 @@ export function useAddToCart() {
151157 } ,
152158 } ,
153159 } as CartLineDetail ,
160+ ...previous . lines . nodes ,
154161 ]
155162 }
156163
@@ -193,6 +200,7 @@ export function useUpdateCartLine() {
193200 updateCartLine ( { data : input } ) ,
194201
195202 onMutate : async ( input ) => {
203+ trackMutationStart ( )
196204 await qc . cancelQueries ( { queryKey : CART_QUERY_KEY } )
197205 const previous = qc . getQueryData < CartDetail | null > ( CART_QUERY_KEY )
198206 if ( previous ) {
@@ -227,6 +235,7 @@ export function useRemoveCartLine() {
227235 mutationFn : ( input : { lineId : string } ) => removeCartLine ( { data : input } ) ,
228236
229237 onMutate : async ( input ) => {
238+ trackMutationStart ( )
230239 await qc . cancelQueries ( { queryKey : CART_QUERY_KEY } )
231240 const previous = qc . getQueryData < CartDetail | null > ( CART_QUERY_KEY )
232241 if ( previous ) {
@@ -259,6 +268,7 @@ export function useApplyDiscountCode() {
259268 mutationFn : ( input : { code : string } ) =>
260269 applyDiscountCode ( { data : { code : input . code } } ) ,
261270 onMutate : async ( ) => {
271+ trackMutationStart ( )
262272 await qc . cancelQueries ( { queryKey : CART_QUERY_KEY } )
263273 } ,
264274 onSuccess : ( cart ) => {
@@ -274,6 +284,7 @@ export function useRemoveDiscountCode() {
274284 mutationKey : CART_MUTATION_KEY ,
275285 mutationFn : ( ) => removeDiscountCode ( ) ,
276286 onMutate : async ( ) => {
287+ trackMutationStart ( )
277288 await qc . cancelQueries ( { queryKey : CART_QUERY_KEY } )
278289 const previous = qc . getQueryData < CartDetail | null > ( CART_QUERY_KEY )
279290 if ( previous ) {
0 commit comments