Build a Form Builder

3 minintermediatereactcodingformbuilder

Quick Answer

Create a dynamic form system with validation, conditional fields, and TypeScript type safety for form values.

Detailed Answer

Build a Form Builder

Create a dynamic form system with validation, conditional fields, and TypeScript type safety for form values.

Solution:

interface FormField {
  name: string;
  type: 'text' | 'email' | 'select' | 'checkbox';
  label: string;
  required?: boolean;
  options?: string[];
  condition?: (values: any) => boolean;
  validation?: (value: any) => string | null;
}

interface FormBuilderProps {
  fields: FormField[];
  onSubmit: (values: any) => void;
}

function FormBuilder({ fields, onSubmit }: FormBuilderProps) {
  const [values, setValues] = useState<Record<string, any>>({});
  const [errors, setErrors] = useState<Record<string, string>>({});
  
  const handleChange = (name: string, value: any) => {
    setValues(prev => ({ ...prev, [name]: value }));
    
    // Clear error when user starts typing
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  };
  
  const validate = () => {
    const newErrors: Record<string, string> = {};
    
    fields.forEach(field => {
      const value = values[field.name];
      
      if (field.required && (!value || value === '')) {
        newErrors[field.name] = `${field.label} is required`;
      }
      
      if (field.validation && value) {
        const error = field.validation(value);
        if (error) newErrors[field.name] = error;
      }
    });
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (validate()) {
      onSubmit(values);
    }
  };
  
  const visibleFields = fields.filter(field => 
    !field.condition || field.condition(values)
  );
  
  return (
    <form onSubmit={handleSubmit}>
      {visibleFields.map(field => (
        <div key={field.name}>
          <label>{field.label}</label>
          {field.type === 'text' || field.type === 'email' ? (
            <input
              type={field.type}
              value={values[field.name] || ''}
              onChange={(e) => handleChange(field.name, e.target.value)}
            />
          ) : field.type === 'select' ? (
            <select
              value={values[field.name] || ''}
              onChange={(e) => handleChange(field.name, e.target.value)}
            >
              <option value="">Select...</option>
              {field.options?.map(option => (
                <option key={option} value={option}>{option}</option>
              ))}
            </select>
          ) : (
            <input
              type="checkbox"
              checked={values[field.name] || false}
              onChange={(e) => handleChange(field.name, e.target.checked)}
            />
          )}
          {errors[field.name] && <span style={{ color: 'red' }}>{errors[field.name]}</span>}
        </div>
      ))}
      <button type="submit">Submit</button>
    </form>
  );
}

// Usage
const MyForm = () => {
  const fields: FormField[] = [
    { name: 'name', type: 'text', label: 'Name', required: true },
    { name: 'email', type: 'email', label: 'Email', required: true, 
      validation: (value) => !/\S+@\S+\.\S+/.test(value) ? 'Invalid email' : null },
    { name: 'country', type: 'select', label: 'Country', options: ['US', 'CA', 'UK'] },
    { name: 'subscribe', type: 'checkbox', label: 'Subscribe to newsletter' },
    { name: 'phone', type: 'text', label: 'Phone', 
      condition: (values) => values.country === 'US' }
  ];
  
  return (
    <FormBuilder
      fields={fields}
      onSubmit={(values) => console.log(values)}
    />
  );
};