# Batch Progress Fix - Visual Flow Diagram
## The Problem: State Management Issue
```
BEFORE FIX - Variable Scope Issue:
═══════════════════════════════════════════════════════════════════════
function handleBatchSync() {
let batchNumber = 1;
let totalImported = 0;
let totalUpdated = 0;
// ❌ NO totalBatches variable at this level
do {
// Update 1: Before API fetch
setSyncProgress({
batchNumber: 1,
totalBatches: ???, // ❌ undefined - no access to it
current: 0,
status: "Processing batch 1..."
});
→ UI shows: "Batch 1" (missing total)
// Fetch from API
response = { totalAvailable: 3000, ... }
// Update 2: After API fetch
const totalBatches = Math.ceil(3000 / 25); // = 120
// ❌ LOCAL VARIABLE - dies at end of iteration
setSyncProgress({
batchNumber: 1,
totalBatches: 120, // ✅ Has it here
current: 25,
status: "Batch 1 complete"
});
→ UI shows: "Batch 1 of 120" ✅
batchNumber++;
// NEXT ITERATION - Batch 2
// Update 1: Before API fetch
setSyncProgress({
batchNumber: 2,
totalBatches: ???, // ❌ Lost! Local variable from previous iteration
current: 25,
status: "Processing batch 2..."
});
→ UI shows: "Batch 2" (missing total again!)
} while (hasMore);
}
```
## The Fix: Persistent State Variable
```
AFTER FIX - Function-Level Variable:
═══════════════════════════════════════════════════════════════════════
function handleBatchSync() {
let batchNumber = 1;
let totalImported = 0;
let totalUpdated = 0;
let totalBatches = undefined; // ✅ FUNCTION-LEVEL VARIABLE
do {
// BATCH 1 - Update 1: Before API fetch
setSyncProgress({
batchNumber: 1,
totalBatches: undefined, // ✅ First batch - unknown yet
current: 0,
status: "Processing batch 1..."
});
→ UI shows: "Batch 1" (expected - we don't know total yet)
// Fetch from API
response = { totalAvailable: 3000, ... }
// Calculate ONCE and STORE in function-level variable
if (response.totalAvailable && !totalBatches) {
totalBatches = Math.ceil(3000 / 25); // = 120
// ✅ Stored in function-level variable - persists!
}
// Update 2: After API fetch
setSyncProgress({
batchNumber: 1,
totalBatches: 120, // ✅ Has it
current: 25,
status: "Batch 1 complete"
});
→ UI shows: "Batch 1 of 120" ✅
batchNumber++;
// BATCH 2 - Update 1: Before API fetch
setSyncProgress({
batchNumber: 2,
totalBatches: 120, // ✅ PRESERVED from previous iteration!
current: 25,
status: "Processing batch 2..."
});
→ UI shows: "Batch 2 of 120" ✅ ✅ ✅
// Fetch from API
response = { totalAvailable: 3000, ... }
// Check if we need to calculate
if (response.totalAvailable && !totalBatches) {
// Skipped - we already have it!
}
// Update 2: After API fetch
setSyncProgress({
batchNumber: 2,
totalBatches: 120, // ✅ Still has it
current: 50,
status: "Batch 2 complete"
});
→ UI shows: "Batch 2 of 120" ✅
} while (hasMore);
}
```
## State Timeline Visualization
```
BATCH 1 LIFECYCLE:
─────────────────────────────────────────────────────────────────
State Variable: totalBatches = undefined
1. Before Fetch:
setSyncProgress({
batchNumber: 1,
totalBatches: undefined, ← Not calculated yet
current: 0
})
UI Display:
┌─────────────────────────────────────┐
│ Processing batch 1... │
│ Batch 1 │ ← Missing total
│ Processed: 0 │
└─────────────────────────────────────┘
2. API Fetch → Response: { totalAvailable: 3000 }
3. Calculate Total:
totalBatches = Math.ceil(3000 / 25) = 120 ← Stored in function scope
4. After Fetch:
setSyncProgress({
batchNumber: 1,
totalBatches: 120, ← Now available!
current: 25
})
UI Display:
┌─────────────────────────────────────┐
│ Batch 1 complete │
│ Batch 1 of 120 │ ← Total appears!
│ Processed: 25 of 3000 │
└─────────────────────────────────────┘
─────────────────────────────────────────────────────────────────
BATCH 2 LIFECYCLE:
─────────────────────────────────────────────────────────────────
State Variable: totalBatches = 120 ← Preserved from Batch 1!
1. Before Fetch:
setSyncProgress({
batchNumber: 2,
totalBatches: 120, ← Already have it!
current: 25
})
UI Display:
┌─────────────────────────────────────┐
│ Processing batch 2... │
│ Batch 2 of 120 │ ← Shows total immediately!
│ Processed: 25 of 3000 │
└─────────────────────────────────────┘
2. API Fetch → Response: { totalAvailable: 3000 }
3. Calculate Total:
if (3000 && !totalBatches) { ← totalBatches = 120, so skip!
// This block doesn't run
}
4. After Fetch:
setSyncProgress({
batchNumber: 2,
totalBatches: 120, ← Still preserved
current: 50
})
UI Display:
┌─────────────────────────────────────┐
│ Batch 2 complete │
│ Batch 2 of 120 │
│ Processed: 50 of 3000 │
└─────────────────────────────────────┘
```
## Code Diff Comparison
```diff
function handleBatchSync(requestId: string) {
let pageToken: string | undefined;
let totalImported = 0;
let totalUpdated = 0;
let totalExported = 0;
let batchNumber = 1;
+ let totalBatches: number | undefined; // ✅ NEW: Track across iterations
const allErrors: string[] = [];
do {
// Before fetch
setSyncProgress({
current: totalImported + totalUpdated,
total: '?',
status: `Processing batch ${batchNumber}...`,
batchNumber,
+ totalBatches, // ✅ Preserve from previous batch
});
// Fetch API...
const data = await fetchAPI();
// After fetch
- const totalBatches = data.totalAvailable
- ? Math.ceil(data.totalAvailable / batchSize)
- : undefined; // ❌ Local variable - lost on next iteration
+ if (data.totalAvailable && !totalBatches) {
+ totalBatches = Math.ceil(data.totalAvailable / batchSize);
+ console.log('Calculated total batches:', totalBatches);
+ } // ✅ Calculate once, store in function-level variable
setSyncProgress({
current: totalImported + totalUpdated,
total: data.totalAvailable || '?',
status: `Batch ${batchNumber} complete`,
batchNumber,
- totalBatches, // ❌ Local variable from this iteration
+ totalBatches, // ✅ Function-level variable, persists
});
batchNumber++;
} while (pageToken);
}
```
## Variable Scope Diagram
```
BEFORE FIX - Local Variable Scope:
═══════════════════════════════════════════════════════════════════════
Function Scope
┌─────────────────────────────────┐
│ totalImported │
│ totalUpdated │
│ batchNumber │
│ │
│ do { │
│ ┌─────────────────────────┐ │
│ │ Iteration 1 Scope │ │
│ │ totalBatches = 120 ❌ │ │ ← Dies here!
│ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ Iteration 2 Scope │ │
│ │ totalBatches = ??? ❌ │ │ ← Lost!
│ └─────────────────────────┘ │
│ } while (hasMore) │
└─────────────────────────────────┘
AFTER FIX - Function-Level Variable:
═══════════════════════════════════════════════════════════════════════
Function Scope
┌─────────────────────────────────┐
│ totalImported │
│ totalUpdated │
│ batchNumber │
│ totalBatches ✅ │ ← Lives here!
│ │
│ do { │
│ ┌─────────────────────────┐ │
│ │ Iteration 1 │ │
│ │ totalBatches = 120 ✅ │ │ ← Sets value
│ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ Iteration 2 │ │
│ │ totalBatches = 120 ✅ │ │ ← Still accessible!
│ └─────────────────────────┘ │
│ } while (hasMore) │
└─────────────────────────────────┘
```
## React State Update Flow
```
BEFORE FIX - Missing Data:
═══════════════════════════════════════════════════════════════════════
Batch 1:
setState({ totalBatches: undefined }) → Render: "Batch 1"
Calculate: totalBatches = 120 (local)
setState({ totalBatches: 120 }) → Render: "Batch 1 of 120" ✅
Batch 2:
setState({ totalBatches: undefined }) → Render: "Batch 2" ❌
Calculate: skipped (local var lost)
setState({ totalBatches: undefined }) → Render: "Batch 2" ❌
AFTER FIX - Preserved Data:
═══════════════════════════════════════════════════════════════════════
Batch 1:
setState({ totalBatches: undefined }) → Render: "Batch 1"
Calculate: totalBatches = 120 (function-level)
setState({ totalBatches: 120 }) → Render: "Batch 1 of 120" ✅
Batch 2:
setState({ totalBatches: 120 }) → Render: "Batch 2 of 120" ✅
Calculate: skipped (already have it)
setState({ totalBatches: 120 }) → Render: "Batch 2 of 120" ✅
```
## Summary
### Root Cause
❌ **Local variable scope** - `totalBatches` was calculated inside loop iteration
❌ **Lost on next iteration** - Local variables don't persist across loop iterations
❌ **React state doesn't help** - We need to preserve the value BEFORE setState
### The Solution
✅ **Function-level variable** - `totalBatches` declared outside loop
✅ **Calculate once** - Only calculate when `!totalBatches` (first time)
✅ **Preserve across iterations** - Variable persists for all loop iterations
✅ **Pass to React state** - setState receives the preserved value
### Key Learning
> When you need a calculated value to persist across loop iterations,
> use a function-level variable, NOT a loop-iteration-level variable.
```javascript
// ❌ Wrong - dies on each iteration
do {
const totalBatches = calculate();
} while (hasMore);
// ✅ Right - persists across iterations
let totalBatches;
do {
if (!totalBatches) {
totalBatches = calculate();
}
} while (hasMore);
```