All debugging logging has been successfully applied to the codebase.
Changes Applied:
- ✅ Added
useEffectimport - ✅ Added logging to track kits query data changes
- ✅ Added logging to track selectedKit state changes with warning for undefined
- ✅ Added logging to kit button click handler
- ✅ Added logging to PadGrid conditional render decision
- ✅ Added logging to SampleBrowser conditional render decision
Changes Applied:
- ✅ Added
useEffectimport - ✅ Added logging for component mount/unmount lifecycle
- ✅ Added logging for kit prop changes
Changes Applied:
- ✅ Added
useEffectimport - ✅ Added logging for component mount/unmount lifecycle
Changes Applied:
- ✅ Added logging to
createKitmutation success with invalidation warning - ✅ Added logging to
assignSamplemutation success - ✅ Added logging to
removeSamplemutation success
cd /Users/bhunt/development/claude/personal/sp404mk2-sample-agent/react-app
npm run dev- Open the application in your browser
- Open Developer Tools (F12 or Cmd+Option+I)
- Go to the Console tab
- Clear the console (Cmd+K or Ctrl+L)
- Navigate to the Kits page
- Click on a kit button to select it
- Watch the console logs in real-time
- Observe if the PadGrid unmounts 1-2 seconds later
Kit button clicked- User clicked a kit button, shows which kit
selectedKit changed- The selectedKit state variable changedWARNING: selectedKit is undefined!- Critical: This will cause unmount
Kits data changed- The kits query data refetched or updated
Conditional render check- Decision to show/hide PadGridSampleBrowser conditional- Decision to show/hide SampleBrowser
createKit success- New kit created, triggering invalidationassignSample success- Sample assigned to pad, triggering invalidationremoveSample success- Sample removed from pad, triggering invalidationInvalidating kits lists query- About to refetch all kits (WATCH THIS)Query invalidation complete- Invalidation finished
Component MOUNTED- PadGrid just appeared in DOMKit prop changed- PadGrid received new kit dataComponent UNMOUNTING- PadGrid being removed from DOM (THE BUG!)
Component MOUNTED- SampleBrowser just appeared in DOMComponent UNMOUNTING- SampleBrowser being removed from DOM (THE BUG!)
When clicking a kit button (SHOULD work like this):
[KIT] Kit button clicked: kitId= 1 kitName= My First Kit timestamp= 2025-11-16T...
[STATE] selectedKit changed: {newValue: 1, timestamp: ..., kitsAvailable: 2, currentKitExists: true}
[RENDER] Conditional render check: {hasCurrentKit: true, selectedKit: 1, decision: 'SHOWING PadGrid'}
[RENDER] SampleBrowser conditional: {hasCurrentKit: true, decision: 'SHOWING SampleBrowser'}
[PADGRID] Component MOUNTED: {kitId: 1, kitName: 'My First Kit', samplesCount: 0}
[SAMPLEBROWSER] Component MOUNTED: {timestamp: ...}
If the bug happens (components unmount 1-2 seconds later):
[KIT] Kit button clicked: kitId= 1 kitName= My First Kit timestamp= ...
[STATE] selectedKit changed: {newValue: 1, ...}
[RENDER] Conditional render check: {hasCurrentKit: true, decision: 'SHOWING PadGrid'}
[PADGRID] Component MOUNTED: {kitId: 1, ...}
[SAMPLEBROWSER] Component MOUNTED: {...}
... 1-2 seconds later ...
[MUTATION] createKit success: {newKitId: 1, ...} <-- OR assignSample, etc.
[MUTATION] Invalidating kits lists query - WARNING: This will refetch ALL kits
[MUTATION] Query invalidation complete
[QUERY] Kits data changed: {kitsCount: 2, ...}
[STATE] selectedKit changed: {newValue: undefined, ...} <-- ROOT CAUSE!
[STATE] WARNING: selectedKit is undefined! This will unmount the builder.
[RENDER] Conditional render check: {hasCurrentKit: false, decision: 'SHOWING empty state'}
[PADGRID] Component UNMOUNTING: {reason: 'Component being removed from DOM'}
[SAMPLEBROWSER] Component UNMOUNTING: {reason: 'Component being removed from DOM'}
Look for the exact log entry that shows selectedKit becoming undefined:
[STATE] selectedKit changed: {newValue: undefined, ...}
[STATE] WARNING: selectedKit is undefined! This will unmount the builder.
Look at the 3-5 log entries BEFORE the selectedKit became undefined:
- Was it a mutation? (
[MUTATION]) - Was it a query refetch? (
[QUERY] Kits data changed) - Was it a render? (
[RENDER])
Note the timestamps to see exact delay:
- Time of kit button click
- Time of unmount
- Time of any mutations/queries in between
Symptom: You'll see this sequence:
[MUTATION] Invalidating kits lists query - WARNING: This will refetch ALL kits
[QUERY] Kits data changed: {kitsCount: ..., ...}
[STATE] selectedKit changed: {newValue: undefined, ...}
Why: When queryClient.invalidateQueries({ queryKey: queryKeys.kits.lists() }) runs, it refetches the kits list. During the refetch, React Query might temporarily set the data to undefined or stale data, causing currentKit to become undefined.
Fix:
- Use optimistic updates instead of full refetch
- Preserve
selectedKitstate during refetches - Use
queryClient.setQueryDatafor immediate cache updates
Symptom: You'll see:
[PADGRID] Component UNMOUNTING: ...
[PADGRID] Component MOUNTED: ... (immediate remount with same kit)
Why: Something higher in the component tree is forcing a full re-render, causing child components to unmount and remount.
Fix:
- Add React.memo() to components
- Check parent component state management
- Add stable key props
Symptom: Multiple mutations firing in rapid succession:
[MUTATION] assignSample success
[MUTATION] createKit success
[MUTATION] removeSample success
Why: If multiple mutations fire quickly, they might invalidate queries in a way that causes state loss.
Fix:
- Debounce mutations
- Queue mutations instead of firing simultaneously
- Use optimistic UI updates
Problem: invalidateQueries causes full refetch, resetting state during refetch window.
Solution Options:
- Option A: Preserve State During Refetch
// In KitsPage.tsx
const { data: kits, isLoading, isRefetching } = useKits();
// Keep the old currentKit during refetch
const currentKit = useMemo(() => {
if (isRefetching && selectedKit) {
// During refetch, try to keep old data
return kits?.items?.find((k) => k.id === selectedKit);
}
return kits?.items?.find((k) => k.id === selectedKit);
}, [kits, selectedKit, isRefetching]);- Option B: Use Optimistic Updates
// In useKits.ts - assignSample
onSuccess: (response, { kitId, assignment }) => {
// Update cache directly instead of refetching
queryClient.setQueryData(
queryKeys.kits.detail(kitId),
(old) => {
if (!old) return old;
return {
...old,
samples: [...old.samples, assignment]
};
}
);
// Don't invalidate - we already updated the cache
},- Option C: Use placeholderData
// In useKits.ts - list kits
export function useKits(params?: { skip?: number; limit?: number }) {
return useQuery({
queryKey: queryKeys.kits.list(params),
queryFn: () => kitsApi.list(params),
placeholderData: (previousData) => previousData, // Keep old data during refetch
});
}Solution: Memoize components
// In kits/PadGrid.tsx
export const PadGrid = React.memo(function PadGrid({ kit, onAssignSample, onRemoveSample }: PadGridProps) {
// ... component code
});
// In kits/SampleBrowser.tsx
export const SampleBrowser = React.memo(function SampleBrowser({ onAddToKit }: SampleBrowserProps) {
// ... component code
});Solution: Queue mutations
// Create a mutation queue
const mutationQueue = useRef<Promise<any>>(Promise.resolve());
const handleAssignSample = async (...args) => {
mutationQueue.current = mutationQueue.current.then(() =>
assignSample.mutateAsync(...)
);
return mutationQueue.current;
};After applying the fix:
- Remove the debug logging (or keep for future debugging)
- Test the user flow:
- Create a new kit
- Click to select it
- Verify PadGrid stays mounted
- Assign samples to pads
- Verify no unmounting happens
- Check edge cases:
- Rapidly clicking between kits
- Assigning multiple samples quickly
- Creating kit while one is selected
If you want to remove the debug logging after identifying the issue:
-
Remove all console.log statements with prefixes:
[KIT][STATE][QUERY][RENDER][MUTATION][PADGRID][SAMPLEBROWSER]
-
Remove added useEffect hooks that only contain logging
-
Restore IIFE wrappers to simple conditionals:
// Change this:
{(() => {
console.log(...);
return currentKit ? <PadGrid /> : <EmptyState />;
})()}
// Back to this:
{currentKit ? <PadGrid /> : <EmptyState />}This debugging patch adds comprehensive logging to trace the exact moment and reason why the KitBuilder interface unmounts. The logs are designed to show:
- WHEN the unmount happens (exact timestamp)
- WHY it happens (what state change or event triggered it)
- WHAT the sequence of events was leading up to it
- WHERE the root cause is (mutation, query, state change, etc.)
Run the test, collect the console logs, and analyze the sequence to identify the exact root cause. The most likely culprit is query invalidation causing a refetch that temporarily resets the kits data to undefined, which causes currentKit to become undefined, which triggers the conditional render to show the empty state instead of the PadGrid.