From 12de4e2ec15550548ca6b8f02071709b04375076 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:51:49 +0000 Subject: [PATCH] Fix error propagation in forms --- docs/FORM_SUBMISSION_PATTERNS.md | 66 ++++++++++++++++------ src/components/admin/DesignerForm.tsx | 3 + src/components/admin/ManufacturerForm.tsx | 3 + src/components/admin/OperatorForm.tsx | 3 + src/components/admin/ParkForm.tsx | 3 + src/components/admin/PropertyOwnerForm.tsx | 3 + src/components/admin/RideForm.tsx | 3 + src/components/admin/RideModelForm.tsx | 3 + 8 files changed, 71 insertions(+), 16 deletions(-) diff --git a/docs/FORM_SUBMISSION_PATTERNS.md b/docs/FORM_SUBMISSION_PATTERNS.md index a155b063..12834444 100644 --- a/docs/FORM_SUBMISSION_PATTERNS.md +++ b/docs/FORM_SUBMISSION_PATTERNS.md @@ -122,6 +122,9 @@ export function EntityForm({ onSubmit, onCancel, initialData }: EntityFormProps) action: initialData?.id ? 'Update Entity' : 'Create Entity', metadata: { entityName: data.name } }); + + // ⚠️ CRITICAL: Re-throw so parent can handle modal state + throw error; } })}> {/* Form fields */} @@ -177,33 +180,64 @@ export function EntityListPage() { ## Error Handling -### Form-Level Errors -Forms use `handleError` utility for errors: +### ⚠️ CRITICAL: Error Propagation Pattern + +Forms MUST re-throw errors after logging them so parent components can respond appropriately (keep modals open, show additional context, etc.). + +**Forms MUST re-throw errors:** ```typescript -try { - await onSubmit(data); } catch (error: unknown) { + // Log error for debugging and show toast to user handleError(error, { - action: 'Create Park', + action: 'Submit Park', userId: user?.id, metadata: { parkName: data.name } }); + + // ⚠️ CRITICAL: Re-throw so parent can handle modal state + throw error; } ``` -### Parent-Level Errors -Parent pages can catch errors for additional handling: +**Why Re-throw?** +- Parent needs to know submission failed +- Modal should stay open so user can retry +- User can fix validation issues and resubmit +- Prevents "success" behavior on failures +- Maintains proper error flow through the app + +### Parent-Level Error Handling + ```typescript -try { - await submitEntityCreation(data, user.id); - toast({ title: "Success" }); - setIsModalOpen(false); -} catch (error) { - // Form already showed error toast - // Parent can log or handle specific cases - if (error.code === 'DUPLICATE_SLUG') { - // Handle specific error +const handleParkSubmit = async (data: FormData) => { + try { + await submitParkCreation(data, user.id); + toast.success('Park submitted for review'); + setIsModalOpen(false); // Only close on success + } catch (error) { + // Error already toasted by form via handleError() + // Modal stays open automatically because we don't close it + // User can fix issues and retry + console.error('Submission failed:', error); } +}; +``` + +**Expected Error Flow:** +1. User submits form → `onSubmit()` called +2. Submission fails → Form catches error +3. Form shows error toast via `handleError()` +4. Form re-throws error to parent +5. Parent's catch block executes +6. Modal stays open (no `setIsModalOpen(false)`) +7. User fixes issue and tries again + +**Common Mistake:** +```typescript +// ❌ WRONG - Error not re-thrown, parent never knows +} catch (error: unknown) { + handleError(error, { action: 'Submit' }); + // Missing: throw error; } ``` diff --git a/src/components/admin/DesignerForm.tsx b/src/components/admin/DesignerForm.tsx index 96107f62..154089a5 100644 --- a/src/components/admin/DesignerForm.tsx +++ b/src/components/admin/DesignerForm.tsx @@ -94,6 +94,9 @@ export function DesignerForm({ onSubmit, onCancel, initialData }: DesignerFormPr action: initialData?.id ? 'Update Designer' : 'Create Designer', metadata: { companyName: data.name } }); + + // Re-throw so parent can handle modal closing + throw error; } })} className="space-y-6"> {/* Basic Information */} diff --git a/src/components/admin/ManufacturerForm.tsx b/src/components/admin/ManufacturerForm.tsx index 3da8f99f..51f2d8ac 100644 --- a/src/components/admin/ManufacturerForm.tsx +++ b/src/components/admin/ManufacturerForm.tsx @@ -98,6 +98,9 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur action: initialData?.id ? 'Update Manufacturer' : 'Create Manufacturer', metadata: { companyName: data.name } }); + + // Re-throw so parent can handle modal closing + throw error; } })} className="space-y-6"> {/* Basic Information */} diff --git a/src/components/admin/OperatorForm.tsx b/src/components/admin/OperatorForm.tsx index 21ef358f..4f68bb0a 100644 --- a/src/components/admin/OperatorForm.tsx +++ b/src/components/admin/OperatorForm.tsx @@ -94,6 +94,9 @@ export function OperatorForm({ onSubmit, onCancel, initialData }: OperatorFormPr action: initialData?.id ? 'Update Operator' : 'Create Operator', metadata: { companyName: data.name } }); + + // Re-throw so parent can handle modal closing + throw error; } })} className="space-y-6"> {/* Basic Information */} diff --git a/src/components/admin/ParkForm.tsx b/src/components/admin/ParkForm.tsx index 7afcd157..2ab92f2a 100644 --- a/src/components/admin/ParkForm.tsx +++ b/src/components/admin/ParkForm.tsx @@ -274,6 +274,9 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: hasNewOwner: !!tempNewPropertyOwner } }); + + // Re-throw so parent can handle modal closing + throw error; } }; diff --git a/src/components/admin/PropertyOwnerForm.tsx b/src/components/admin/PropertyOwnerForm.tsx index bdf4331b..6cba1a56 100644 --- a/src/components/admin/PropertyOwnerForm.tsx +++ b/src/components/admin/PropertyOwnerForm.tsx @@ -94,6 +94,9 @@ export function PropertyOwnerForm({ onSubmit, onCancel, initialData }: PropertyO action: initialData?.id ? 'Update Property Owner' : 'Create Property Owner', metadata: { companyName: data.name } }); + + // Re-throw so parent can handle modal closing + throw error; } })} className="space-y-6"> {/* Basic Information */} diff --git a/src/components/admin/RideForm.tsx b/src/components/admin/RideForm.tsx index 3c91d114..5e910256 100644 --- a/src/components/admin/RideForm.tsx +++ b/src/components/admin/RideForm.tsx @@ -352,6 +352,9 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: hasNewModel: !!tempNewRideModel } }); + + // Re-throw so parent can handle modal closing + throw error; } }; diff --git a/src/components/admin/RideModelForm.tsx b/src/components/admin/RideModelForm.tsx index 7343aa61..81796b5e 100644 --- a/src/components/admin/RideModelForm.tsx +++ b/src/components/admin/RideModelForm.tsx @@ -113,6 +113,9 @@ export function RideModelForm({ handleError(error, { action: initialData?.id ? 'Update Ride Model' : 'Create Ride Model' }); + + // Re-throw so parent can handle modal closing + throw error; } };