From 186a1ba0861ccfcb813c931dca89994ce6fc27e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Nov 2025 10:19:54 +0000 Subject: [PATCH] fix: CRITICAL ROOT CAUSE - Prevent wizard component recreation on every keystroke MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ROOT CAUSE ANALYSIS: The input focus loss bug was caused by the wizard steps being recreated on EVERY SINGLE RENDER of UnifiedAddWizard, which happened on EVERY KEYSTROKE. DETAILED PROBLEM FLOW: 1. User types in name field → handleDataChange called 2. handleDataChange calls setWizardData → UnifiedAddWizard re-renders 3. Line 127: steps={getWizardSteps()} called on every render 4. getWizardSteps() calls InventoryWizardSteps(wizardData, setWizardData) 5. Returns NEW array with NEW component function references: component: (props) => 6. React compares old component ref with new ref, sees they're different 7. React UNMOUNTS old component and MOUNTS new component 8. Input element is DESTROYED and RECREATED → LOSES FOCUS This happened on EVERY keystroke because: - wizardData updates on every keystroke - getWizardSteps() runs on every render - New component functions created every time - React sees different function reference = different component type SOLUTION: Used useMemo to memoize the wizardSteps array so it's only recreated when selectedItemType changes, NOT when wizardData changes. const wizardSteps = useMemo(() => { // ... generate steps }, [selectedItemType, handleItemTypeSelect]); Now the step component functions maintain the same reference across renders, so React keeps the same component instance mounted, preserving input focus. IMPACT: ✅ Inputs no longer lose focus while typing ✅ Component state is preserved between keystrokes ✅ No more unmount/remount cycles on every keystroke ✅ Dramatically improved performance (no unnecessary component recreation) This was the TRUE root cause - the previous fixes helped but didn't solve the fundamental architectural issue of recreating components on every render. --- .../domain/unified-wizard/UnifiedAddWizard.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/domain/unified-wizard/UnifiedAddWizard.tsx b/frontend/src/components/domain/unified-wizard/UnifiedAddWizard.tsx index 5ec04d0f..15ae7fb7 100644 --- a/frontend/src/components/domain/unified-wizard/UnifiedAddWizard.tsx +++ b/frontend/src/components/domain/unified-wizard/UnifiedAddWizard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useMemo } from 'react'; import { Sparkles } from 'lucide-react'; import { WizardModal, WizardStep } from '../../ui/WizardModal/WizardModal'; import { ItemTypeSelector, ItemType } from './ItemTypeSelector'; @@ -57,7 +57,9 @@ export const UnifiedAddWizard: React.FC = ({ ); // Get wizard steps based on selected item type - const getWizardSteps = (): WizardStep[] => { + // CRITICAL: Memoize the steps to prevent component recreation on every render + // Without this, every keystroke causes the component to unmount/remount, losing focus + const wizardSteps = useMemo((): WizardStep[] => { if (!selectedItemType) { // Step 0: Item Type Selection return [ @@ -95,7 +97,7 @@ export const UnifiedAddWizard: React.FC = ({ default: return []; } - }; + }, [selectedItemType, handleItemTypeSelect]); // Only recreate when item type changes, NOT when wizardData changes // Get wizard title based on selected item type const getWizardTitle = (): string => { @@ -124,7 +126,7 @@ export const UnifiedAddWizard: React.FC = ({ onClose={handleClose} onComplete={handleWizardComplete} title={getWizardTitle()} - steps={getWizardSteps()} + steps={wizardSteps} icon={} size="xl" />