Kit builder interface unmounts 1-2 seconds after mounting when kit button is clicked. Need to trace exact sequence of events leading to unmount.
Location: /Users/bhunt/development/claude/personal/sp404mk2-sample-agent/react-app/src/pages/KitsPage.tsx
BEFORE:
<Button
variant={selectedKit === kit.id ? 'default' : 'outline'}
onClick={() => setSelectedKit(kit.id)}
className="pr-8"
>
{kit.name}
</Button>AFTER:
<Button
variant={selectedKit === kit.id ? 'default' : 'outline'}
onClick={() => {
console.log('[KIT] Kit button clicked: kitId=', kit.id, 'kitName=', kit.name, 'timestamp=', new Date().toISOString());
setSelectedKit(kit.id);
}}
className="pr-8"
>
{kit.name}
</Button>What this logs: Exact moment user clicks a kit button, which kit was clicked, and timestamp.
BEFORE:
const removeSample = useRemoveSample();
const currentKit = kits?.items?.find((k) => k.id === selectedKit);AFTER:
const removeSample = useRemoveSample();
// DEBUG: Track selectedKit state changes
React.useEffect(() => {
console.log('[STATE] selectedKit changed:', {
newValue: selectedKit,
timestamp: new Date().toISOString(),
kitsAvailable: kits?.items?.length || 0,
currentKitExists: !!kits?.items?.find((k) => k.id === selectedKit)
});
if (selectedKit === undefined) {
console.log('[STATE] WARNING: selectedKit is undefined! This will unmount the builder.');
}
}, [selectedKit, kits]);
const currentKit = kits?.items?.find((k) => k.id === selectedKit);What this logs: Every time selectedKit state changes, logs new value and whether it will cause unmount.
BEFORE:
{currentKit ? (
<PadGrid
kit={currentKit}
onAssignSample={handleAssignSample}
onRemoveSample={handleRemoveSample}
/>
) : (
<div className="h-full flex items-center justify-center">AFTER:
{(() => {
console.log('[RENDER] Conditional render check:', {
hasCurrentKit: !!currentKit,
selectedKit,
timestamp: new Date().toISOString(),
decision: currentKit ? 'SHOWING PadGrid' : 'SHOWING empty state'
});
return currentKit ? (
<PadGrid
kit={currentKit}
onAssignSample={handleAssignSample}
onRemoveSample={handleRemoveSample}
/>
) : (
<div className="h-full flex items-center justify-center">What this logs: Every render cycle, logs whether PadGrid will be shown or hidden.
BEFORE:
{/* Sample Browser Sidebar */}
{currentKit && (
<div className="w-96 flex-shrink-0">AFTER:
{/* Sample Browser Sidebar */}
{(() => {
console.log('[RENDER] SampleBrowser conditional:', {
hasCurrentKit: !!currentKit,
timestamp: new Date().toISOString(),
decision: currentKit ? 'SHOWING SampleBrowser' : 'HIDING SampleBrowser'
});
return currentKit && (
<div className="w-96 flex-shrink-0">What this logs: Whether SampleBrowser will be rendered or hidden.
BEFORE:
const { data: kits, isLoading } = useKits();
const createKit = useCreateKit();AFTER:
const { data: kits, isLoading } = useKits();
// DEBUG: Track kits data changes
React.useEffect(() => {
console.log('[QUERY] Kits data changed:', {
kitsCount: kits?.items?.length || 0,
isLoading,
timestamp: new Date().toISOString(),
kitIds: kits?.items?.map(k => k.id) || []
});
}, [kits, isLoading]);
const createKit = useCreateKit();What this logs: Every time the kits query data changes, which could trigger re-renders.
Location: /Users/bhunt/development/claude/personal/sp404mk2-sample-agent/react-app/src/hooks/useKits.ts
BEFORE:
onSuccess: (_, { kitId }) => {
queryClient.invalidateQueries({ queryKey: queryKeys.kits.detail(kitId) });
},AFTER:
onSuccess: (_, { kitId }) => {
console.log('[MUTATION] assignSample success - invalidating queries:', {
kitId,
timestamp: new Date().toISOString(),
queryKey: queryKeys.kits.detail(kitId)
});
queryClient.invalidateQueries({ queryKey: queryKeys.kits.detail(kitId) });
console.log('[MUTATION] Query invalidation complete');
},What this logs: When sample assignment triggers query invalidation (which could refetch and reset state).
BEFORE:
onSuccess: (_, { kitId }) => {
queryClient.invalidateQueries({ queryKey: queryKeys.kits.detail(kitId) });
},AFTER:
onSuccess: (_, { kitId }) => {
console.log('[MUTATION] removeSample success - invalidating queries:', {
kitId,
timestamp: new Date().toISOString()
});
queryClient.invalidateQueries({ queryKey: queryKeys.kits.detail(kitId) });
console.log('[MUTATION] Query invalidation complete');
},What this logs: When sample removal triggers query invalidation.
BEFORE:
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.kits.lists() });
},AFTER:
onSuccess: (newKit) => {
console.log('[MUTATION] createKit success:', {
newKitId: newKit.id,
timestamp: new Date().toISOString()
});
console.log('[MUTATION] Invalidating kits lists query');
queryClient.invalidateQueries({ queryKey: queryKeys.kits.lists() });
console.log('[MUTATION] WARNING: This will refetch ALL kits - might reset state');
},What this logs: When creating a kit triggers a full kits list refetch (THIS IS LIKELY THE CULPRIT).
Location: /Users/bhunt/development/claude/personal/sp404mk2-sample-agent/react-app/src/components/kits/PadGrid.tsx
BEFORE:
export function PadGrid({ kit, onAssignSample, onRemoveSample }: PadGridProps) {
const [activeBank, setActiveBank] = useState<'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J'>('A');
const getPadAssignment = (bank: string, number: number) => {AFTER:
export function PadGrid({ kit, onAssignSample, onRemoveSample }: PadGridProps) {
const [activeBank, setActiveBank] = useState<'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J'>('A');
// DEBUG: Track component lifecycle
React.useEffect(() => {
console.log('[PADGRID] Component MOUNTED:', {
kitId: kit.id,
kitName: kit.name,
samplesCount: kit.samples.length,
timestamp: new Date().toISOString()
});
return () => {
console.log('[PADGRID] Component UNMOUNTING:', {
kitId: kit.id,
kitName: kit.name,
timestamp: new Date().toISOString(),
reason: 'Component being removed from DOM'
});
};
}, []);
// DEBUG: Track kit prop changes
React.useEffect(() => {
console.log('[PADGRID] Kit prop changed:', {
kitId: kit.id,
kitName: kit.name,
samplesCount: kit.samples.length,
timestamp: new Date().toISOString()
});
}, [kit]);
const getPadAssignment = (bank: string, number: number) => {What this logs: When PadGrid mounts, unmounts, and when its kit prop changes.
Location: /Users/bhunt/development/claude/personal/sp404mk2-sample-agent/react-app/src/components/kits/SampleBrowser.tsx
BEFORE:
export function SampleBrowser({ onAddToKit }: SampleBrowserProps) {
const [search, setSearch] = useState('');
const [selectedGenre, setSelectedGenre] = useState<string>();
const [bpmRange, setBpmRange] = useState<[number, number]>();
const { data: samples, isLoading } = useSamples({AFTER:
export function SampleBrowser({ onAddToKit }: SampleBrowserProps) {
const [search, setSearch] = useState('');
const [selectedGenre, setSelectedGenre] = useState<string>();
const [bpmRange, setBpmRange] = useState<[number, number]>();
// DEBUG: Track component lifecycle
React.useEffect(() => {
console.log('[SAMPLEBROWSER] Component MOUNTED:', {
timestamp: new Date().toISOString()
});
return () => {
console.log('[SAMPLEBROWSER] Component UNMOUNTING:', {
timestamp: new Date().toISOString(),
reason: 'Component being removed from DOM'
});
};
}, []);
const { data: samples, isLoading } = useSamples({What this logs: When SampleBrowser mounts and unmounts.
-
Add React import to all modified files if not already present:
import React from 'react';
-
Apply changes sequentially to each file
-
Test the debugging:
- Open browser console (F12)
- Click on a kit button
- Watch the console logs in real-time
- Observe the exact sequence of events
[KIT] Kit button clicked: kitId=1 kitName=Test Kit timestamp=...
[STATE] selectedKit changed: {newValue: 1, ...}
[RENDER] Conditional render check: {hasCurrentKit: true, decision: 'SHOWING PadGrid'}
[RENDER] SampleBrowser conditional: {hasCurrentKit: true, decision: 'SHOWING SampleBrowser'}
[PADGRID] Component MOUNTED: {kitId: 1, ...}
[SAMPLEBROWSER] Component MOUNTED: {...}
[KIT] Kit button clicked: kitId=1 kitName=Test 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: ... <-- OR assignSample, etc.
[MUTATION] Invalidating kits lists query
[MUTATION] WARNING: This will refetch ALL kits - might reset state
[QUERY] Kits data changed: {kitsCount: 2, ...}
[STATE] selectedKit changed: {newValue: undefined, ...} <-- CULPRIT!
[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'}
- Exact moment selectedKit becomes undefined
- What event precedes it (mutation? query refetch?)
- Timing between events (is it exactly after a query invalidation?)
- Whether kits data changes (does refetch cause state reset?)
Based on code analysis, these are the most likely culprits:
useAssignSampleinvalidatesqueryKeys.kits.detail(kitId)useCreateKitinvalidatesqueryKeys.kits.lists()- Problem: If the list query refetches, it might be resetting component state
- React Query might be causing re-render with stale data
currentKitbecomes undefined during refetch window
- Something higher up the tree is forcing full page re-render
- State is lost during parent remount
Once logs show the exact sequence:
-
If it's query invalidation:
- Change to optimistic updates instead of full refetch
- Use
queryClient.setQueryDatato update cache directly - Preserve
selectedKitstate during refetches
-
If it's component unmounting:
- Find what's causing parent unmount
- Add key props to prevent unnecessary unmounts
-
If it's state timing:
- Add defensive checks for
currentKitexistence - Implement loading states during refetch
- Add defensive checks for