From c8f9045a387716b3e0e7327ba645c367422d8d9c Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Tue, 24 Feb 2026 21:43:05 +0530 Subject: [PATCH] fix: Improve robustness and error handling for dynamic form fields and sections with safe data access and type coercion. --- .../agent/edit/components/DynamicField.tsx | 150 +++++++++++------- .../agent/edit/components/DynamicSection.tsx | 141 ++++++++++------ .../edit/components/RepeatableSection.tsx | 49 +++++- src/app/(agent)/agent/edit/page.tsx | 13 +- 4 files changed, 234 insertions(+), 119 deletions(-) diff --git a/src/app/(agent)/agent/edit/components/DynamicField.tsx b/src/app/(agent)/agent/edit/components/DynamicField.tsx index fcc894b..0279051 100644 --- a/src/app/(agent)/agent/edit/components/DynamicField.tsx +++ b/src/app/(agent)/agent/edit/components/DynamicField.tsx @@ -29,6 +29,18 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF onChange(field.slug, newValue); }; + // Safe accessors for field properties that might be null/undefined + const fieldName = field.name || 'Field'; + const fieldSlug = field.slug || ''; + const fieldPlaceholder = field.placeholder || ''; + const safeOptions = Array.isArray(field.options) ? field.options : []; + + // Safe value coercion helpers + const safeString = (v: unknown): string => (typeof v === 'string' ? v : ''); + const safeStringArray = (v: unknown): string[] => (Array.isArray(v) ? v.filter((item): item is string => typeof item === 'string') : []); + const safeNumber = (v: unknown, fallback: number): number => (typeof v === 'number' ? v : fallback); + const safeFileArray = (v: unknown): UploadedFile[] => (Array.isArray(v) ? v : []); + const renderField = () => { switch (field.fieldType) { case 'REPEATER': @@ -38,9 +50,9 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF case 'TEXT': return ( handleChange(val)} required={field.isRequired} error={error} @@ -50,9 +62,9 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF case 'TEXTAREA': return ( handleChange(val)} required={field.isRequired} rows={4} @@ -62,10 +74,10 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF case 'NUMBER': return ( handleChange(val)} required={field.isRequired} error={error} @@ -77,9 +89,9 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF case 'DATE': return ( handleChange(val)} required={field.isRequired} error={error} @@ -89,7 +101,7 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF case 'CHECKBOX': return ( handleChange(checked)} /> @@ -99,7 +111,7 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF return ( ); @@ -108,7 +120,7 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF return ( ); @@ -116,11 +128,11 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF case 'SELECT': return ( handleChange(val)} - options={field.options?.map(opt => ({ value: opt.value, label: opt.label })) || []} - placeholder={field.placeholder || `Select ${field.name.toLowerCase()}`} + options={safeOptions.map(opt => ({ value: opt.value, label: opt.label }))} + placeholder={fieldPlaceholder || `Select ${fieldName.toLowerCase()}`} /> ); @@ -128,7 +140,7 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF return ( ); @@ -137,7 +149,7 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF return ( ); @@ -146,13 +158,13 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF return (
handleChange(tags)} - placeholder={field.placeholder || `Add ${field.name.toLowerCase()}`} + placeholder={fieldPlaceholder || `Add ${fieldName.toLowerCase()}`} />
); @@ -162,8 +174,8 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF return ( ); @@ -171,9 +183,9 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF default: return ( handleChange(val)} required={field.isRequired} error={error} @@ -182,18 +194,28 @@ export default function DynamicField({ field, value, onChange, error }: DynamicF } }; - const fieldContent = renderField(); + // Wrap in try-catch to prevent individual field errors from crashing the entire page + try { + const fieldContent = renderField(); - // Don't render anything for null fields (like REPEATER) - if (fieldContent === null) { - return null; + // Don't render anything for null fields (like REPEATER) + if (fieldContent === null) { + return null; + } + + return ( +
+ {fieldContent} +
+ ); + } catch (err) { + console.error(`Error rendering field "${fieldName}" (${field.fieldType}):`, err); + return ( +
+

Failed to render field: {fieldName}

+
+ ); } - - return ( -
- {fieldContent} -
- ); } // Checkbox Group Component @@ -204,10 +226,13 @@ interface CheckboxGroupProps { } function CheckboxGroup({ field, value, onChange }: CheckboxGroupProps) { + const safeValue = Array.isArray(value) ? value : []; + const safeOptions = Array.isArray(field.options) ? field.options : []; + const handleToggle = (optionValue: string) => { - const newValue = value.includes(optionValue) - ? value.filter(v => v !== optionValue) - : [...value, optionValue]; + const newValue = safeValue.includes(optionValue) + ? safeValue.filter(v => v !== optionValue) + : [...safeValue, optionValue]; onChange(newValue); }; @@ -216,15 +241,15 @@ function CheckboxGroup({ field, value, onChange }: CheckboxGroupProps) { return (
- {field.options?.map((option) => { - const isChecked = value.includes(option.value); + {safeOptions.map((option) => { + const isChecked = safeValue.includes(option.value); return (