Proje: Okul.com.tr CRM · Hub: Okul.com.tr CRM — Conventions
Kural
Form alanlarındaki state değişiklikleri tetiklenecek debounce’lu bir API hesaplaması varsa (ör. /admin/checkouts/calculate), scheduleCalculate() çağrısını handler içinden yapma — ilgili state’leri izleyen bir useEffect kullan.
// YANLIŞ — stale closure, ilk değişiklikte API çağrılmaz
const handleProductChange = (idx, val) => {
updateRow(idx, {product_price_id: val, unit_price: ...});
scheduleCalculate(); // setState async; rows snapshot'ı henüz güncel değil
};
// DOĞRU — commit sonrası tetikleniyor
useEffect(() => {
scheduleCalculate();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rows, customer, campaignId, isCashPayment]);Why
setState asenkron. Handler içinde setRows(...) sonrası aynı tick’te scheduleCalculate() çağrılırsa, setTimeout callback’i o handler’ın render’ından kapanışa giren rows’u görüyor — yeni seçilen değer henüz commit olmamış. Sonuç: collectKnownProducts() boş döner, runCalculate API çağrısını skip eder, özet 0,00 ₺‘de kalır.
useCallback’lenmiş handler’lar (deps eksik tutulmuş) aynı sorunu kalıcı hale getirir: ilk render’ın closure’una sıkışırlar.
How to Apply
- Hesaplama state’e bağımlıysa, ilgili state’leri
useEffectdeps’ine koy - Handler’lar sadece state’i güncellesin; hesaplamayı tetiklemesin
- Debounce timer (
useRef) hâlâ effect içinde yönetilebilir - Reference uygulama:
src/pages/checkouts/basket.tsx—useEffect[rows, customer, campaignId, isCashPayment]izliyor
Tetikleyen İncident
/sales/checkouts/basket özet kısmı 0,00 ₺‘de kalıyordu; calculate endpoint’i çağrılmıyordu çünkü scheduleCalculate handler’lardan eski rows ile çağrılıyordu.
Related
- hook-katalogu — useDebounce, useGenericList vs.
- form-pattern — Form state yönetimi