diff --git a/frontend/src/features/vehicles/components/VehicleForm.tsx b/frontend/src/features/vehicles/components/VehicleForm.tsx index 03dfbbf..ca289cb 100644 --- a/frontend/src/features/vehicles/components/VehicleForm.tsx +++ b/frontend/src/features/vehicles/components/VehicleForm.tsx @@ -95,6 +95,7 @@ export const VehicleForm: React.FC = ({ const [loadingDropdowns, setLoadingDropdowns] = useState(false); const hasInitialized = useRef(false); const isInitializing = useRef(false); + const isVinDecoding = useRef(false); // Track previous values for cascade change detection (replaces useState) const prevYear = useRef(undefined); const prevMake = useRef(''); @@ -224,8 +225,8 @@ export const VehicleForm: React.FC = ({ // Load makes when year changes useEffect(() => { - // Skip during initialization - if (isInitializing.current) return; + // Skip during initialization or VIN decoding + if (isInitializing.current || isVinDecoding.current) return; if (watchedYear && watchedYear !== prevYear.current) { const loadMakes = async () => { @@ -262,8 +263,8 @@ export const VehicleForm: React.FC = ({ // Load models when make changes useEffect(() => { - // Skip during initialization - if (isInitializing.current) return; + // Skip during initialization or VIN decoding + if (isInitializing.current || isVinDecoding.current) return; if (watchedMake && watchedYear && watchedMake !== prevMake.current) { const loadModels = async () => { @@ -297,8 +298,8 @@ export const VehicleForm: React.FC = ({ // Load trims when model changes useEffect(() => { - // Skip during initialization - if (isInitializing.current) return; + // Skip during initialization or VIN decoding + if (isInitializing.current || isVinDecoding.current) return; if (watchedModel && watchedYear && watchedMake && watchedModel !== prevModel.current) { const loadTrims = async () => { @@ -329,8 +330,8 @@ export const VehicleForm: React.FC = ({ // Load engines and transmissions when trim changes useEffect(() => { - // Skip during initialization - if (isInitializing.current) return; + // Skip during initialization or VIN decoding + if (isInitializing.current || isVinDecoding.current) return; if (watchedTrim && watchedYear && watchedMake && watchedModel && watchedTrim !== prevTrim.current) { const loadEnginesAndTransmissions = async () => { @@ -443,7 +444,17 @@ export const VehicleForm: React.FC = ({ try { const decoded = await vehiclesApi.decodeVin(vin); - // Only populate empty fields (preserve existing user input) + // Prevent cascade useEffects from clearing values during VIN decode + isVinDecoding.current = true; + setLoadingDropdowns(true); + + // Track which values we're setting (only populate empty fields) + const yearToSet = !watchedYear && decoded.year.value ? decoded.year.value : watchedYear; + const makeToSet = !watchedMake && decoded.make.value ? decoded.make.value : watchedMake; + const modelToSet = !watchedModel && decoded.model.value ? decoded.model.value : watchedModel; + const trimToSet = !watchedTrim && decoded.trimLevel.value ? decoded.trimLevel.value : watchedTrim; + + // Set form values if (!watchedYear && decoded.year.value) { setValue('year', decoded.year.value); } @@ -463,16 +474,38 @@ export const VehicleForm: React.FC = ({ setValue('transmission', decoded.transmission.value); } - // Body type, drive type, fuel type - check if fields are empty and we have values - const currentDriveType = watch('driveType'); - const currentFuelType = watch('fuelType'); + // Load dropdown options hierarchically (like edit mode initialization) + // This ensures dropdowns have the right options for the decoded values + if (yearToSet) { + prevYear.current = yearToSet; + const makesData = await vehiclesApi.getMakes(yearToSet); + setMakes(makesData); - if (!currentDriveType && decoded.driveType.nhtsaValue) { - // For now just show hint - user can select from dropdown - } - if (!currentFuelType && decoded.fuelType.nhtsaValue) { - // For now just show hint - user can select from dropdown + if (makeToSet) { + prevMake.current = makeToSet; + const modelsData = await vehiclesApi.getModels(yearToSet, makeToSet); + setModels(modelsData); + + if (modelToSet) { + prevModel.current = modelToSet; + const trimsData = await vehiclesApi.getTrims(yearToSet, makeToSet, modelToSet); + setTrims(trimsData); + + if (trimToSet) { + prevTrim.current = trimToSet; + const [enginesData, transmissionsData] = await Promise.all([ + vehiclesApi.getEngines(yearToSet, makeToSet, modelToSet, trimToSet), + vehiclesApi.getTransmissions(yearToSet, makeToSet, modelToSet, trimToSet) + ]); + setEngines(enginesData); + setTransmissions(transmissionsData); + } + } + } } + + setLoadingDropdowns(false); + isVinDecoding.current = false; } catch (error: any) { console.error('VIN decode failed:', error); if (error.response?.data?.error === 'TIER_REQUIRED') { @@ -484,6 +517,8 @@ export const VehicleForm: React.FC = ({ } } finally { setIsDecoding(false); + setLoadingDropdowns(false); + isVinDecoding.current = false; } };