import React, { createContext, useContext, useState, useCallback, useMemo, ReactNode, useRef, useEffect } from 'react'; import { DialogMode } from '../types'; // Add FormRef type definition export type FormRef = React.RefObject; // Form API interface - Ensure getValues is explicitly a function type interface FormAPI { getValues: () => Record; getValidatedValues: () => Promise>; reset: (values?: Record) => void; validate: () => Promise; isDirty: () => boolean; isValid: () => boolean; getErrors: () => Record; setError?: (field: string, message: string) => void; submit?: () => Promise; } // Form state interface interface FormState { isValid: boolean; isDirty: boolean; errors: Record; } // Context interface export interface FormHandlingContextType { // Form registration registerForm: (type: DialogMode, api: FormAPI) => void; unregisterForm: (type: DialogMode) => void; // Form state getters isFormDirty: (type: DialogMode) => boolean; isFormValid: (type: DialogMode) => boolean; getFormValues: (type: DialogMode) => Record; getFormErrors: (type: DialogMode) => Record; // Form state setters setFormDirty: (isDirty: boolean, type: DialogMode) => void; setFormValid: (isValid: boolean, type: DialogMode) => void; setFormErrors: (errors: Record, type: DialogMode) => void; // Form operations resetForm: (type: DialogMode, values?: Record) => void; validateForm: (type: DialogMode) => Promise; validateAndGetFormData: (type: DialogMode) => Promise<{ isValid: boolean; data: Record | null; errors: Record | null; }>; submitForm: (type: DialogMode) => Promise; // Active form type activeFormType: DialogMode | null; setActiveFormType: (type: DialogMode | null) => void; } // Create context with a default value const FormHandlingContext = createContext(null); /** * Provider component for form handling functionality */ export function FormHandlingProvider({ children }: { children: ReactNode }) { // Track registered forms - use ref-based approach to prevent update loops const [forms, setForms] = useState>({}); const formsRef = useRef>({}); // Track form states with a ref to prevent circular updates const [formStates, setFormStates] = useState>({}); const formStatesRef = useRef>({}); // Track active form type const [activeFormType, setActiveFormType] = useState(null); // CRITICAL FIX: Remove the problematic useEffect hooks that cause infinite loops // Instead, update refs synchronously during state updates // Register a form - prevent duplicate registrations and infinite loops const registerForm = useCallback((type: DialogMode, api: FormAPI) => { console.log(`[FormHandlingContext] Registering form for ${type}`); // Update both state and ref in the same update cycle setForms(prev => { const newForms = { ...prev, [type]: api }; formsRef.current = newForms; // Update ref immediately return newForms; }); // Initialize form state if needed setFormStates(prev => { // Don't update if already exists with same state if (prev[type] && JSON.stringify(prev[type]) === JSON.stringify({ isValid: true, isDirty: false, errors: {} })) { return prev; } const newStates = { ...prev, [type]: { isValid: true, isDirty: false, errors: {} } }; formStatesRef.current = newStates; // Update ref immediately return newStates; }); // Set as active form if no active form exists - prevent loops setActiveFormType(current => current !== null ? current : type); }, []); // Remove all dependencies to prevent loops // Unregister form with more safety checks const unregisterForm = useCallback((type: DialogMode) => { console.log(`[FormHandlingContext] Unregistering form for ${type}`); if (!type) return; // Safety check // Remove form API reference setForms(prev => { if (!prev[type]) return prev; const newForms = { ...prev }; delete newForms[type]; formsRef.current = newForms; // Update ref immediately return newForms; }); // Remove form state setFormStates(prev => { if (!prev[type]) return prev; const newStates = { ...prev }; delete newStates[type]; formStatesRef.current = newStates; // Update ref immediately return newStates; }); // Clear active form type if this was the active one setActiveFormType(current => current === type ? null : current); }, []); // Remove dependencies to prevent loops // CRITICAL FIX: Use only refs for these getters to prevent loops const isFormDirty = useCallback((type: DialogMode) => { const formState = formStatesRef.current[type]; if (formState !== undefined) { return formState.isDirty; } const form = formsRef.current[type]; if (form?.isDirty) { try { return form.isDirty(); } catch (error) { console.error(`[FormHandlingContext] Error checking if form ${type} is dirty:`, error); } } return false; }, []); // Remove dependencies const isFormValid = useCallback((type: DialogMode) => { const formState = formStatesRef.current[type]; if (formState !== undefined) { return formState.isValid; } const form = formsRef.current[type]; if (form?.isValid) { try { const isValid = form.isValid(); return isValid; } catch (error) { console.error(`[FormHandlingContext] Error checking if form ${type} is valid:`, error); } } return true; }, []); // Remove dependencies const getFormValues = useCallback((type: DialogMode) => { const form = formsRef.current[type]; if (form && typeof form.getValues === 'function') { try { return form.getValues(); } catch (error) { console.error(`[FormHandlingContext] Error getting form values for ${type}:`, error); } } return {}; }, []); // Remove dependencies const getFormErrors = useCallback((type: DialogMode) => { const formState = formStatesRef.current[type]; if (formState) { return formState.errors; } const form = formsRef.current[type]; if (form?.getErrors) { return form.getErrors(); } return {}; }, []); // Remove dependencies // CRITICAL FIX: Prevent setState loops in these functions const setFormDirty = useCallback((isDirty: boolean, type: DialogMode) => { if (type === undefined || type === null) return; const currentState = formStatesRef.current[type]; if (currentState && currentState.isDirty === isDirty) return; setFormStates(prev => { const current = prev[type]; if (!current || current.isDirty === isDirty) return prev; const newStates = { ...prev, [type]: { ...current, isDirty } }; formStatesRef.current = newStates; // Update ref immediately return newStates; }); }, []); // Remove dependencies const setFormValid = useCallback((isValid: boolean, type: DialogMode) => { if (type === undefined || type === null) return; const currentState = formStatesRef.current[type]; if (currentState && currentState.isValid === isValid) return; setFormStates(prev => { const current = prev[type]; if (!current || current.isValid === isValid) return prev; const newStates = { ...prev, [type]: { ...current, isValid } }; formStatesRef.current = newStates; // Update ref immediately return newStates; }); }, []); // Remove dependencies const setFormErrors = useCallback((errors: Record, type: DialogMode) => { if (type === undefined || type === null) return; const currentState = formStatesRef.current[type]; if (currentState && JSON.stringify(currentState.errors) === JSON.stringify(errors)) return; setFormStates(prev => { const current = prev[type]; if (!current) return prev; const newStates = { ...prev, [type]: { ...current, errors } }; formStatesRef.current = newStates; // Update ref immediately return newStates; }); }, []); // Remove dependencies // CRITICAL FIX: Use formsRef.current instead of forms state const resetForm = useCallback((type: DialogMode, values?: Record) => { const form = formsRef.current[type]; if (form?.reset) { // CRITICAL FIX: Only reset when values are provided // This prevents unwanted resets during error scenarios if (values !== undefined) { form.reset(values); } } // Reset form state only when explicitly requested if (values !== undefined) { setFormStates(prev => { const current = prev[type] || { isValid: true, isDirty: false, errors: {} }; const newStates = { ...prev, [type]: { ...current, isDirty: false, errors: {} } }; formStatesRef.current = newStates; // Update ref immediately return newStates; }); } }, []); // Remove dependencies const validateForm = useCallback(async (type: DialogMode) => { const form = formsRef.current[type]; if (!form) return true; try { if (form.validate) { const isValid = await form.validate(); // Update form state without causing loops setFormValid(isValid, type); return isValid; } return isFormValid(type); } catch (error) { console.error(`[FormHandlingContext] Error validating form for ${type}:`, error); return false; } }, [isFormValid, setFormValid]); // Keep only essential dependencies // Validate and get form data - completely rewritten to fix registration issues const validateAndGetFormData = useCallback(async (type: DialogMode) => { console.log(`[FormHandlingContext] Validating form for ${type}. Available forms:`, Object.keys(formsRef.current)); // Get form reference with retry logic let form = formsRef.current[type]; // If form not found, wait a bit and try again (registration might be in progress) if (!form) { console.log(`[FormHandlingContext] Form not immediately found for ${type}, retrying...`); await new Promise(resolve => setTimeout(resolve, 100)); form = formsRef.current[type]; } // Handle missing form if (!form) { console.error(`[FormHandlingContext] Form not found for ${type} after retry. Available forms:`, Object.keys(formsRef.current)); return { isValid: false, data: null, errors: { _form: `Form not found for ${type}` } as Record }; } try { // APPROACH 1: Use getValidatedValues if available if (typeof form.getValidatedValues === 'function') { try { console.log(`[FormHandlingContext] Getting validated values for ${type} using getValidatedValues`); const data = await form.getValidatedValues(); console.log(`[FormHandlingContext] Received data:`, data); // Ensure we have actual data if (!data || (typeof data === 'object' && Object.keys(data).length === 0)) { console.warn(`[FormHandlingContext] No data returned from getValidatedValues for ${type}`); } return { isValid: true, data: data || {}, errors: null }; } catch (error) { console.error(`[FormHandlingContext] Validation failed for ${type}:`, error); // Handle validation errors let errors: Record = { _form: 'Validation failed' }; if (typeof form.getErrors === 'function') { try { const formErrors = form.getErrors(); if (formErrors && typeof formErrors === 'object') { errors = formErrors; if (!('_form' in errors)) { errors._form = 'Validation failed'; } } } catch (err) { console.error(`[FormHandlingContext] Error getting validation errors:`, err); } } setFormValid(false, type); setFormErrors(errors, type); return { isValid: false, data: null, errors }; } } // APPROACH 2: Use validate + getValues if available if (typeof form.validate === 'function' && typeof form.getValues === 'function') { console.log(`[FormHandlingContext] Using validate + getValues for ${type}`); let isValid = false; try { isValid = await form.validate(); } catch (err) { console.error(`[FormHandlingContext] Error validating form:`, err); isValid = false; } if (isValid) { try { const data = form.getValues(); console.log(`[FormHandlingContext] Got values:`, data); // Ensure we have data if (!data || (typeof data === 'object' && Object.keys(data).length === 0)) { console.warn(`[FormHandlingContext] Empty data from getValues for ${type}`); } return { isValid: true, data: data || {}, errors: null }; } catch (err) { console.error(`[FormHandlingContext] Error getting form values:`, err); return { isValid: false, data: null, errors: { _form: 'Error getting form values' } as Record }; } } else { // Handle validation failure let errors: Record = { _form: 'Validation failed' }; if (typeof form.getErrors === 'function') { try { const formErrors = form.getErrors(); if (formErrors && typeof formErrors === 'object') { errors = { ...formErrors }; if (!('_form' in errors)) { errors._form = 'Validation failed'; } } } catch (err) { console.error(`[FormHandlingContext] Error getting validation errors:`, err); } } return { isValid: false, data: null, errors }; } } // APPROACH 3: Just try to get values directly if no validation is available console.log(`[FormHandlingContext] Using fallback approach for ${type}`); if (typeof form.getValues === 'function') { try { const data = form.getValues(); console.log(`[FormHandlingContext] Got values via fallback:`, data); return { isValid: true, data: data || {}, errors: null }; } catch (err) { console.error(`[FormHandlingContext] Error getting form values in fallback:`, err); } } // Last resort - return empty data console.warn(`[FormHandlingContext] No viable method to get form data for ${type}`); return { isValid: true, data: {}, errors: null }; } catch (error) { console.error(`[FormHandlingContext] Unexpected error in validateAndGetFormData:`, error); return { isValid: false, data: null, errors: { _form: error instanceof Error ? error.message : 'Unknown error' } as Record }; } }, [setFormValid, setFormErrors]); // Submit form const submitForm = useCallback(async (type: DialogMode) => { const form = forms[type]; if (!form) return false; try { // Use form's submit method if available if (form.submit) { return await form.submit(); } // Fall back to validate + getValues const isValid = await validateForm(type); return isValid; } catch (error) { console.error(`[FormHandlingContext] Error submitting form for ${type}:`, error); return false; } }, [forms, validateForm]); // CRITICAL FIX: Create context value with stable references const contextValue = useMemo(() => ({ registerForm, unregisterForm, isFormDirty, isFormValid, getFormValues, getFormErrors, setFormDirty, setFormValid, setFormErrors, resetForm, validateForm, validateAndGetFormData, submitForm, activeFormType, setActiveFormType }), [ // Only include the essential dependencies that actually change the behavior activeFormType ]); return ( {children} ); } /** * Hook to access the form handling context * @throws Error if used outside FormHandlingProvider */ export const useFormHandling = () => { const context = useContext(FormHandlingContext); if (!context) { throw new Error('useFormHandling must be used within a FormHandlingProvider'); } return context; }; /** * Higher-order component to wrap form components * Works with both regular components and ref-forwarded components */ export function withFormHandling

( Component: React.ComponentType

, formType: DialogMode ) { const WithFormHandling = (props: P) => { const formHandling = useFormHandling(); // Set active form type when component mounts React.useEffect(() => { formHandling.setActiveFormType(formType); return () => { // Clear active form type when component unmounts if it matches if (formHandling.activeFormType === formType) { formHandling.setActiveFormType(null); } }; }, []); return ; }; WithFormHandling.displayName = `WithFormHandling(${Component.displayName || Component.name || 'Component'})`; return WithFormHandling; }