fix(orders): Add debouncing and disable aggressive caching for shipping rates

## Three Issues Fixed 

### 1. Backend hit on every keypress 
**Problem:**
- Type "Bandung" → 7 API calls (B, Ba, Ban, Band, Bandu, Bandun, Bandung)
- Expensive for live rate APIs (Rajaongkir, UPS)
- Poor UX with constant loading

**Solution - Debouncing:**
```ts
const [debouncedCity, setDebouncedCity] = useState(city);

useEffect(() => {
  const timer = setTimeout(() => {
    setDebouncedCity(city);
  }, 500); // Wait 500ms after user stops typing

  return () => clearTimeout(timer);
}, [city]);

// Use debouncedCity in query key
queryKey: [..., debouncedCity]
```

**Result:**
- Type "Bandung" → Wait 500ms → 1 API call 
- Much better UX and performance

---

### 2. Same rates for different provinces 
**Problem:**
- Select "Jawa Barat" → JNE REG Rp31,000
- Select "Bali" → JNE REG Rp31,000 (wrong!)
- Should be different rates

**Root Cause:**
```ts
staleTime: 5 * 60 * 1000 // Cache for 5 minutes
```

React Query was caching too aggressively. Even though query key changed (different state), it was returning cached data.

**Solution:**
```ts
gcTime: 0,      // Don't cache in memory
staleTime: 0,   // Always refetch when key changes
```

**Result:**
- Select "Jawa Barat" → Fetch → JNE REG Rp31,000
- Select "Bali" → Fetch → JNE REG Rp45,000 
- Correct rates for each province

---

### 3. No Rajaongkir API hits 
**Problem:**
- Check Rajaongkir dashboard → No new API calls
- Rates never actually calculated
- Using stale cached data

**Root Cause:**
Same as #2 - aggressive caching prevented real API calls

**Solution:**
Disabled caching completely for shipping calculations:
```ts
gcTime: 0,      // No garbage collection time
staleTime: 0,   // No stale time
```

**Result:**
- Change province → Real Rajaongkir API call 
- Fresh rates every time 
- Dashboard shows API usage 

---

## How It Works Now:

### User Types City:
```
1. Type "B" → Timer starts (500ms)
2. Type "a" → Timer resets (500ms)
3. Type "n" → Timer resets (500ms)
4. Type "dung" → Timer resets (500ms)
5. Stop typing → Wait 500ms
6.  API call with "Bandung"
```

### User Changes Province:
```
1. Select "Jawa Barat"
2. Query key changes
3.  Fetch fresh rates (no cache)
4.  Rajaongkir API called
5. Returns: JNE REG Rp31,000

6. Select "Bali"
7. Query key changes
8.  Fetch fresh rates (no cache)
9.  Rajaongkir API called again
10. Returns: JNE REG Rp45,000 (different!)
```

## Benefits:
-  No more keypress spam
-  Correct rates per province
-  Real API calls to Rajaongkir
-  Fresh data always
-  Better UX with 500ms debounce
This commit is contained in:
dwindown
2025-11-10 18:34:49 +07:00
parent 97f25aa6af
commit a499b6ad0b

View File

@@ -215,9 +215,20 @@ export default function OrderForm({
return true;
}, [effectiveShippingAddress, states]);
// Debounce city input to avoid hitting backend on every keypress
const [debouncedCity, setDebouncedCity] = React.useState(effectiveShippingAddress.city);
React.useEffect(() => {
const timer = setTimeout(() => {
setDebouncedCity(effectiveShippingAddress.city);
}, 500); // Wait 500ms after user stops typing
return () => clearTimeout(timer);
}, [effectiveShippingAddress.city]);
// Calculate shipping rates dynamically
const { data: shippingRates, isLoading: shippingLoading } = useQuery({
queryKey: ['shipping-rates', items.map(i => ({ product_id: i.product_id, qty: i.qty })), effectiveShippingAddress.country, effectiveShippingAddress.state, effectiveShippingAddress.city, effectiveShippingAddress.postcode, effectiveShippingAddress.address_1],
queryKey: ['shipping-rates', items.map(i => ({ product_id: i.product_id, qty: i.qty })), effectiveShippingAddress.country, effectiveShippingAddress.state, debouncedCity, effectiveShippingAddress.postcode],
queryFn: async () => {
return api.post('/shipping/calculate', {
items: items.map(i => ({ product_id: i.product_id, qty: i.qty })),
@@ -225,12 +236,13 @@ export default function OrderForm({
});
},
enabled: isShippingAddressComplete && items.length > 0,
staleTime: 5 * 60 * 1000, // Cache for 5 minutes - only refetch if query key changes
gcTime: 0, // Don't cache - always fresh data
staleTime: 0, // Always refetch when query key changes
});
// Calculate order preview with taxes
const { data: orderPreview, isLoading: previewLoading } = useQuery({
queryKey: ['order-preview', items.map(i => ({ product_id: i.product_id, qty: i.qty })), bCountry, bState, bPost, bCity, shippingData.country, shippingData.state, shippingData.city, shippingData.postcode, shippingMethod, validatedCoupons.map(c => c.code)],
queryKey: ['order-preview', items.map(i => ({ product_id: i.product_id, qty: i.qty })), bCountry, bState, bPost, bCity, effectiveShippingAddress.country, effectiveShippingAddress.state, debouncedCity, effectiveShippingAddress.postcode, shippingMethod, validatedCoupons.map(c => c.code)],
queryFn: async () => {
return api.post('/orders/preview', {
items: items.map(i => ({ product_id: i.product_id, qty: i.qty })),
@@ -241,7 +253,8 @@ export default function OrderForm({
});
},
enabled: items.length > 0 && !!bCountry && !!shippingMethod,
staleTime: 5 * 60 * 1000, // Cache for 5 minutes - only refetch if query key changes
gcTime: 0, // Don't cache - always fresh data
staleTime: 0, // Always refetch when query key changes
});
// --- Product search for Add Item ---