import React, { useState, useReducer, useCallback, useEffect } from 'react';
const initialState = { values: {}, errors: {}, touched: {}, isSubmitting: false, isValid: false };
function formReducer(state, action) { switch (action.type) { case 'update_field': return { ...state, values: { ...state.values, [action.field]: action.value } }; case 'set_error': return { ...state, errors: { ...state.errors, [action.field]: action.error } }; case 'set_touched': return { ...state, touched: { ...state.touched, [action.field]: true } }; case 'set_validity': return { ...state, isValid: action.isValid }; case 'set_submitting': return { ...state, isSubmitting: action.value }; case 'reset': return initialState; default: return state; } }
function useForm(fields, validate) { const [state, dispatch] = useReducer(formReducer, initialState); useEffect(() => { const errors = validate(state.values); const isValid = Object.keys(errors).length === 0; dispatch({ type: 'set_validity', isValid }); }, [state.values, validate]); const handleChange = useCallback((e) => { const { name, value, type, checked, files } = e.target; const fieldValue = type === 'checkbox' ? checked : type === 'file' ? files[0] : value; dispatch({ type: 'update_field', field: name, value: fieldValue }); if (state.touched[name]) { const fieldError = validateField(name, fieldValue); dispatch({ type: 'set_error', field: name, error: fieldError }); } }, [state.touched]); const handleBlur = useCallback((e) => { const { name, value } = e.target; dispatch({ type: 'set_touched', field: name }); const error = validateField(name, value); dispatch({ type: 'set_error', field: name, error }); }, []); const handleSubmit = useCallback(async (onSubmit) => { const errors = validate(state.values); Object.entries(errors).forEach(([field, error]) => { dispatch({ type: 'set_error', field, error }); }); if (Object.keys(errors).length === 0) { dispatch({ type: 'set_submitting', value: true }); try { await onSubmit(state.values); } catch (error) { console.error('Form submission error:', error); } finally { dispatch({ type: 'set_submitting', value: false }); } } }, [state.values, validate]); const resetForm = useCallback(() => { dispatch({ type: 'reset' }); }, []); const getFieldProps = useCallback((fieldName) => { const field = fields.find(f => f.name === fieldName); return { name: fieldName, value: state.values[fieldName] || '', error: state.errors[fieldName], touched: state.touched[fieldName], onChange: handleChange, onBlur: handleBlur, ...field.props }; }, [fields, state, handleChange, handleBlur]); return { values: state.values, errors: state.errors, touched: state.touched, isSubmitting: state.isSubmitting, isValid: state.isValid, handleChange, handleBlur, handleSubmit, resetForm, getFieldProps }; }
function validateField(name, value, fields) { const field = fields.find(f => f.name === name); if (!field) return ''; if (field.required && !value) { return field.requiredMessage || `${field.label} is required`; } if (field.minLength && value.length < field.minLength) { return `${field.label} must be at least ${field.minLength} characters`; } if (field.maxLength && value.length > field.maxLength) { return `${field.label} must be at most ${field.maxLength} characters`; } if (field.pattern && !field.pattern.test(value)) { return field.patternMessage || `${field.label} is invalid`; } return ''; }
function validateForm(values, fields) { const errors = {}; fields.forEach(field => { const error = validateField(field.name, values[field.name], fields); if (error) { errors[field.name] = error; } }); return errors; }
function Form({ fields, onSubmit, className }) { const form = useForm(fields, (values) => validateForm(values, fields)); return ( <form className={className} onSubmit={(e) => { e.preventDefault(); form.handleSubmit(onSubmit); }} > {fields.map(field => { const props = form.getFieldProps(field.name); return ( <div key={field.name} className={`form-group ${props.error ? 'has-error' : ''}`}> <label htmlFor={field.name}> {field.label} {field.required && <span className="required">*</span>} </label> {field.type === 'select' ? ( <select {...props}> <option value="">Select {field.label}</option> {field.options.map(option => ( <option key={option.value} value={option.value}> {option.label} </option> ))} </select> ) : field.type === 'textarea' ? ( <textarea {...props} rows={field.rows || 3} /> ) : field.type === 'checkbox' ? ( <div className="checkbox-group"> <input type="checkbox" {...props} /> <label>{field.label}</label> </div> ) : field.type === 'radio' ? ( <div className="radio-group"> {field.options.map(option => ( <div key={option.value} className="radio-option"> <input type="radio" id={`${field.name}-${option.value}`} name={field.name} value={option.value} checked={props.value === option.value} onChange={props.onChange} /> <label htmlFor={`${field.name}-${option.value}`}> {option.label} </label> </div> ))} </div> ) : ( <input type={field.type} {...props} /> )} {props.touched && props.error && ( <div className="error-message">{props.error}</div> )} </div> ); })} <div className="form-actions"> <button type="submit" disabled={form.isSubmitting || !form.isValid} className={`submit-btn ${form.isSubmitting ? 'loading' : ''}`} > {form.isSubmitting ? 'Submitting...' : 'Submit'} </button> <button type="button" onClick={form.resetForm} className="reset-btn" > Reset </button> </div> </form> ); }
function UserRegistration() { const fields = [ { name: 'username', label: 'Username', type: 'text', required: true, minLength: 3, maxLength: 20 }, { name: 'email', label: 'Email', type: 'email', required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }, { name: 'password', label: 'Password', type: 'password', required: true, minLength: 8 }, { name: 'confirmPassword', label: 'Confirm Password', type: 'password', required: true }, { name: 'country', label: 'Country', type: 'select', required: true, options: [ { value: 'us', label: 'United States' }, { value: 'uk', label: 'United Kingdom' }, { value: 'ca', label: 'Canada' }, { value: 'au', label: 'Australia' } ] }, { name: 'newsletter', label: 'Subscribe to newsletter', type: 'checkbox' }, { name: 'gender', label: 'Gender', type: 'radio', required: true, options: [ { value: 'male', label: 'Male' }, { value: 'female', label: 'Female' }, { value: 'other', label: 'Other' } ] } ]; const handleSubmit = async (values) => { console.log('Form submitted:', values); alert('Registration successful!'); }; return ( <div className="registration-form"> <h2>User Registration</h2> <Form fields={fields} onSubmit={handleSubmit} className="registration-form" /> </div> ); }
|