Graneet Form LogoGraneet form

Performance Optimization

Learn how to optimize graneet-form for maximum performance in large and complex applications

Performance Optimization

Learn how to optimize graneet-form for maximum performance in large and complex applications.

Understanding Graneet-Form's Performance Model

Subscription-Based Architecture

Graneet-form uses a subscription system where components only re-render when their watched fields change:

// ✅ Only re-renders when 'name' or 'email' changes
const { name, email } = useFieldsWatch(form, ['name', 'email']);

// ❌ Re-renders when ANY field changes
const allValues = useFieldsWatch(form, undefined);

Field Registration Overhead

Each field has a small registration cost, but the benefits outweigh the overhead:

  • Registration: One-time cost when field mounts
  • Value Updates: Optimized O(1) lookups for subscribers
  • Validation: Runs only on changed fields
  • Cleanup: Automatic when field unmounts

Optimization Strategies

1. Selective Field Watching

Watch Only Required Fields

// ❌ Inefficient - watches all fields
function FormSummary() {
  const form = useFormContext<LargeFormData>();
  const allValues = useFieldsWatch(form, undefined);
  
  return <div>Name: {allValues.name}</div>;
}

// ✅ Efficient - watches only needed fields
function FormSummary() {
  const form = useFormContext<LargeFormData>();
  const { name } = useFieldsWatch(form, ['name']);
  
  return <div>Name: {name}</div>;
}

Use Appropriate Watch Modes

// For real-time UI updates (search, live preview)
const { searchQuery } = useFieldsWatch(form, ['searchQuery']);

// For less critical updates (summaries, auto-save)
const { title, description } = useFieldsWatch(form, ['title', 'description'], { mode: 'onBlur' });

2. Lazy Field Registration

Conditional Field Rendering

interface ConditionalFormData {
  showAdvanced: boolean;
  // ... many other fields
}

function ConditionalForm() {
  const form = useFormContext<ConditionalFormData>();
  const { showAdvanced } = useFieldsWatch(form, ['showAdvanced']);

  return (
    <Form form={form}>
      {/* Always render basic fields */}
      <BasicFields />
      
      {/* Only register expensive fields when needed */}
      {showAdvanced && <AdvancedFields />}
    </Form>
  );
}

function AdvancedFields() {
  return (
    <>
      {/* These 50+ fields only register when showAdvanced is true */}
      <Field name="advancedField1" render={/* ... */} />
      <Field name="advancedField2" render={/* ... */} />
      {/* ... more fields */}
    </>
  );
}

Virtualized Forms

For forms with hundreds of fields:

function VirtualizedForm() {
  const [visibleRange, setVisibleRange] = useState({ start: 0, end: 20 });
  const form = useFormContext<MassiveFormData>();

  return (
    <div className="virtualized-container">
      {/* Only render fields in visible range */}
      {Array.from({ length: visibleRange.end - visibleRange.start })
        .map((_, index) => {
          const fieldIndex = visibleRange.start + index;
          return (
            <Field
              key={fieldIndex}
              name={`field_${fieldIndex}`}
              render={/* ... */}
            />
          );
        })}
    </div>
  );
}

3. Optimized Validation

Validation Order Optimization

Place cheap validations first, expensive ones last:

<Field name="email" render={/* ... */}>
  {/* Fast validations first */}
  <Rule validationFn={isRequired} message="Email is required" />
  <Rule validationFn={isValidEmailFormat} message="Invalid email format" />
  <Rule validationFn={isReasonableLength} message="Email too long" />
  
  {/* Expensive async validation last */}
  <Rule 
    validationFn={checkEmailAvailability}
    message="Email already exists"
    isDebounced={true}
  />
</Field>

Debounced Validation

// Custom debounced validation hook
function useDebouncedValidation(validationFn: Function, delay: number = 500) {
  const timeoutRef = useRef<NodeJS.Timeout>();
  
  return useCallback((value: any) => {
    return new Promise((resolve) => {
      clearTimeout(timeoutRef.current);
      timeoutRef.current = setTimeout(() => {
        resolve(validationFn(value));
      }, delay);
    });
  }, [validationFn, delay]);
}

// Usage
function AsyncValidatedField() {
  const debouncedValidation = useDebouncedValidation(expensiveApiCall, 1000);
  
  return (
    <Field name="username" render={/* ... */}>
      <Rule
        validationFn={debouncedValidation}
        message="Username validation failed"
        isDebounced={true}
      />
    </Field>
  );
}

4. Component Memoization

Memoize Field Components

// Memoize stable field components
const MemoizedTextField = React.memo(<T extends FieldValues, K extends keyof T>({
  name,
  label,
  placeholder
}: {
  name: K;
  label: string;
  placeholder: string;
}) => (
  <Field<T, K>
    name={name}
    render={(fieldProps, fieldState) => (
      <div>
        <label>{label}</label>
        <input
          placeholder={placeholder}
          value={fieldProps.value || ''}
          onChange={(e) => fieldProps.onChange(e.target.value)}
          onBlur={fieldProps.onBlur}
          onFocus={fieldProps.onFocus}
        />
        {!fieldState.isPristine && fieldState.validationStatus.status === 'invalid' && (
          <span>{fieldState.validationStatus.message}</span>
        )}
      </div>
    )}
  />
));

// Usage
<MemoizedTextField 
  name="email" 
  label="Email Address" 
  placeholder="Enter email" 
/>

Memoize Form Sections

const MemoizedFormSection = React.memo(({ 
  title,
  children 
}: {
  title: string;
  children: React.ReactNode;
}) => (
  <div className="form-section">
    <h3>{title}</h3>
    {children}
  </div>
));

function OptimizedForm() {
  return (
    <Form form={form}>
      <MemoizedFormSection title="Basic Information">
        <BasicFields />
      </MemoizedFormSection>
      
      <MemoizedFormSection title="Contact Details">
        <ContactFields />
      </MemoizedFormSection>
    </Form>
  );
}

5. Efficient State Updates

Batch Form Updates

// ❌ Multiple individual updates
function updateUserProfile(userData: UserData) {
  form.setFormValues({ name: userData.name });
  form.setFormValues({ email: userData.email });
  form.setFormValues({ phone: userData.phone });
  // Each call triggers re-renders
}

// ✅ Single batched update
function updateUserProfile(userData: UserData) {
  form.setFormValues({
    name: userData.name,
    email: userData.email,
    phone: userData.phone
  });
  // Single re-render for all subscribers
}

Minimize Form Method Calls in Renders

// ❌ Calling getFormValues in render
function FormDisplay() {
  const form = useFormContext();
  const values = form.getFormValues(); // Called on every render
  
  return <div>{JSON.stringify(values)}</div>;
}

// ✅ Use optimized watching hooks
function FormDisplay() {
  const form = useFormContext();
  const values = useFieldsWatch(form, undefined); // Optimized subscription
  
  return <div>{JSON.stringify(values)}</div>;
}

Custom Performance Hooks

Optimized Multi-field Watcher

function useOptimizedFieldWatcher<T extends FieldValues>(
  form: FormContextApi<T>,
  criticalFields: (keyof T)[],
  normalFields: (keyof T)[]
) {
  // Critical fields update immediately (onChange)
  const criticalValues = useFieldsWatch(form, criticalFields);
  
  // Normal fields update on blur only
  const normalValues = useFieldsWatch(form, normalFields, { mode: 'onBlur' });
  
  return useMemo(() => ({
    ...criticalValues,
    ...normalValues
  }), [criticalValues, normalValues]);
}

// Usage
function SmartFormWatcher() {
  const form = useFormContext<FormData>();
  
  const values = useOptimizedFieldWatcher(
    form,
    ['searchQuery', 'liveFilter'], // Need immediate updates
    ['description', 'notes', 'tags'] // Blur updates are sufficient
  );
  
  return <FormPreview values={values} />;
}

Debounced Form Watcher

function useDebouncedFormValues<T extends FieldValues>(
  form: FormContextApi<T>,
  fields: (keyof T)[],
  delay: number = 300
) {
  const values = useFieldsWatch(form, fields);
  const [debouncedValues, setDebouncedValues] = useState(values);
  
  useEffect(() => {
    const timeout = setTimeout(() => {
      setDebouncedValues(values);
    }, delay);
    
    return () => clearTimeout(timeout);
  }, [values, delay]);
  
  return debouncedValues;
}

// Usage for expensive operations
function ExpensiveFormProcessor() {
  const form = useFormContext<FormData>();
  
  // Only process after 500ms of no changes
  const debouncedValues = useDebouncedFormValues(form, ['data'], 500);
  
  const processedData = useMemo(() => {
    return performExpensiveCalculation(debouncedValues.data);
  }, [debouncedValues.data]);
  
  return <div>{processedData}</div>;
}

Memory Management

Cleanup Strategies

function FormWithCleanup() {
  const form = useForm<FormData>();
  
  useEffect(() => {
    // Cleanup function
    return () => {
      // Form automatically cleans up field subscriptions
      // But you might want to clean up additional resources
      clearTimeout(autoSaveTimeout.current);
      cancelPendingRequests();
    };
  }, []);

  return <Form form={form}>{/* ... */}</Form>;
}

Prevent Memory Leaks

function SafeAsyncField() {
  const [loading, setLoading] = useState(false);
  const mountedRef = useRef(true);
  
  useEffect(() => {
    return () => {
      mountedRef.current = false;
    };
  }, []);
  
  const handleAsyncValidation = useCallback(async (value: string) => {
    setLoading(true);
    
    try {
      const result = await validateAsync(value);
      
      // Only update state if component is still mounted
      if (mountedRef.current) {
        setLoading(false);
      }
      
      return result;
    } catch (error) {
      if (mountedRef.current) {
        setLoading(false);
      }
      return false;
    }
  }, []);
  
  return (
    <Field name="asyncField" render={/* ... */}>
      <Rule
        validationFn={handleAsyncValidation}
        message="Validation failed"
        isDebounced={true}
      />
    </Field>
  );
}

Performance Monitoring

Custom Performance Hook

function useFormPerformance<T extends FieldValues>(form: FormContextApi<T>) {
  const [metrics, setMetrics] = useState({
    renderCount: 0,
    lastRenderTime: Date.now(),
    averageRenderTime: 0
  });
  
  useEffect(() => {
    const startTime = Date.now();
    
    setMetrics(prev => {
      const renderTime = startTime - prev.lastRenderTime;
      const newRenderCount = prev.renderCount + 1;
      const newAverageRenderTime = 
        (prev.averageRenderTime * (newRenderCount - 1) + renderTime) / newRenderCount;
      
      return {
        renderCount: newRenderCount,
        lastRenderTime: startTime,
        averageRenderTime: newAverageRenderTime
      };
    });
  });
  
  return metrics;
}

// Usage (development only)
function PerformanceMonitor() {
  const form = useFormContext();
  const metrics = useFormPerformance(form);
  
  if (process.env.NODE_ENV !== 'development') {
    return null;
  }
  
  return (
    <div className="performance-monitor">
      <div>Renders: {metrics.renderCount}</div>
      <div>Avg Render Time: {metrics.averageRenderTime.toFixed(2)}ms</div>
    </div>
  );
}

Bundle Size Optimization

Tree Shaking

Import only what you need:

// ❌ Imports entire library
import * as GraneetForm from 'graneet-form';

// ✅ Import only needed components
import { useForm, Form, Field, Rule } from 'graneet-form';

Code Splitting

// Lazy load complex form sections
const AdvancedFormSection = React.lazy(() => import('./AdvancedFormSection'));
const PaymentSection = React.lazy(() => import('./PaymentSection'));

function OptimizedForm() {
  const [activeSection, setActiveSection] = useState('basic');
  
  return (
    <Form form={form}>
      <BasicSection />
      
      <Suspense fallback={<div>Loading...</div>}>
        {activeSection === 'advanced' && <AdvancedFormSection />}
        {activeSection === 'payment' && <PaymentSection />}
      </Suspense>
    </Form>
  );
}

Performance Best Practices Summary

✅ Do's

  1. Watch specific fields instead of all fields
  2. Use appropriate watch modes (onChange vs onBlur)
  3. Lazy register fields in conditional sections
  4. Debounce expensive validations
  5. Memoize stable components
  6. Batch form updates
  7. Clean up resources properly

❌ Don'ts

  1. Don't watch all fields unless absolutely necessary
  2. Don't call form methods in render functions
  3. Don't create new objects in render functions
  4. Don't ignore cleanup in async operations
  5. Don't validate on every keystroke for expensive operations
  6. Don't render all fields if many are conditional

Performance Checklist

  • Fields watch only required data
  • Expensive validations are debounced
  • Conditional sections use lazy registration
  • Components are memoized where appropriate
  • Form updates are batched
  • Memory leaks are prevented
  • Bundle size is optimized

Measuring Performance

Use React DevTools Profiler to measure:

  1. Render frequency of form components
  2. Time spent rendering form sections
  3. Memory usage over time
  4. JavaScript heap size growth

Monitor these metrics in your applications to identify performance bottlenecks and optimize accordingly.