# Reform Form Watcher

Reform provides a powerful form watching system that allows you to observe and react to changes in form fields and form state. This document covers the form watching capabilities of Reform.

## Table of Contents

- [Overview](#overview)
- [Field Watching](#field-watching)
- [Form State Observation](#form-state-observation)
- [Combined Capabilities](#combined-capabilities)
- [Auto-Save Implementation](#auto-save-implementation)
- [API Reference](#api-reference)

## Overview

Reform's form watcher system allows you to:

1. **Watch specific fields** for changes with debounce support
2. **Observe form state** changes (dirty, valid, errors, etc.)
3. **Combine field and state watching** for advanced use cases
4. **Create auto-save functionality** with minimal code

## Field Watching

Watch specific fields for changes and react accordingly:

```tsx
import { useReform, useReformWatcher } from '@reform/core';

type UserForm = {
  email: string;
  username: string;
  password: string;
};

const UserFormComponent = () => {
  const form = useReform<UserForm>({
    defaultData: {
      email: '',
      username: '',
      password: '',
    },
    minGroups: 1,
  });

  // Initialize the watcher
  const watcher = useReformWatcher(form);

  // Set up field watchers
  useEffect(() => {
    // Watch email field with debounce
    const unsubscribeEmail = watcher.watchField({
      field: 'email',
      callback: async (value, prevValue, groupIndex) => {
        if (value && value !== prevValue) {
          const isAvailable = await checkEmailAvailability(value);
          if (!isAvailable) {
            form.setError(groupIndex, 'email', {
              type: 'manual',
              message: 'Email is already taken',
            });
          } else {
            form.clearErrors(groupIndex, 'email');
          }
        }
      },
      debounce: 500, // Wait 500ms after typing stops
    });

    // Watch username field
    const unsubscribeUsername = watcher.watchField({
      field: 'username',
      callback: (value) => {
        console.log('Username changed:', value);
      },
    });

    // Clean up watchers when component unmounts
    return () => {
      unsubscribeEmail();
      unsubscribeUsername();
    };
  }, [watcher, form]);

  return (
    <form>
      <div>
        <label>Email</label>
        <input {...form.register(0, 'email')} />
        {form.getFieldError(0, 'email') && (
          <p className="error">{form.getFieldError(0, 'email')?.message}</p>
        )}
      </div>
      <div>
        <label>Username</label>
        <input {...form.register(0, 'username')} />
      </div>
      <div>
        <label>Password</label>
        <input type="password" {...form.register(0, 'password')} />
      </div>
    </form>
  );
};
```

## Form State Observation

Observe changes in form state:

```tsx
import { useReform, useReformWatcher } from '@reform/core';

const FormWithStateObservation = () => {
  const form = useReform<UserForm>(config);
  const [formStatus, setFormStatus] = useState('');
  
  // Initialize the watcher with state change callbacks
  const watcher = useReformWatcher(form, {
    onDirtyChange: (isDirty) => {
      console.log('Form dirty state changed:', isDirty);
    },
    onErrorsChange: (hasErrors, errors) => {
      console.log('Form errors changed:', hasErrors, errors);
    },
    onValidChange: (isValid) => {
      console.log('Form validity changed:', isValid);
    },
  });
  
  // Subscribe to form state changes
  useEffect(() => {
    const subscription = watcher.subscribeToState((state, prevState) => {
      if (state.isDirty && !prevState?.isDirty) {
        setFormStatus('Form has unsaved changes');
      } else if (state.isSubmitting) {
        setFormStatus('Submitting form...');
      } else if (state.isSubmitSuccessful) {
        setFormStatus('Form submitted successfully!');
      } else if (Object.keys(state.errors).length > 0) {
        setFormStatus('Form has errors');
      } else if (state.isValid) {
        setFormStatus('Form is valid');
      } else {
        setFormStatus('');
      }
    });
    
    return subscription.unsubscribe;
  }, [watcher]);
  
  return (
    <div>
      <form>{/* Form fields */}</form>
      {formStatus && <div className="form-status">{formStatus}</div>}
      <div className="form-state-info">
        <p>Is Dirty: {watcher.isDirty() ? 'Yes' : 'No'}</p>
        <p>Is Valid: {watcher.isValid() ? 'Yes' : 'No'}</p>
        <p>Has Errors: {watcher.hasErrors() ? 'Yes' : 'No'}</p>
        <p>Is Submitting: {watcher.isSubmitting() ? 'Yes' : 'No'}</p>
      </div>
    </div>
  );
};
```

## Combined Capabilities

Combine field watching with form state observation:

```tsx
import { useReform, useReformWatcher } from '@reform/core';

const CombinedWatcherForm = () => {
  const form = useReform<UserForm>(config);
  const watcher = useReformWatcher(form);
  
  useEffect(() => {
    // Watch a field and also react to form state changes
    const unsubscribe = watcher.watchFieldWithState(
      {
        field: 'email',
        callback: (value) => {
          console.log('Email changed:', value);
        },
      },
      (state) => {
        if (state.isSubmitting) {
          console.log('Form is submitting with email:', form.getFieldValue(0, 'email'));
        }
      }
    );
    
    return unsubscribe;
  }, [watcher, form]);
  
  // Watch a field only when the form is valid
  useEffect(() => {
    const unsubscribe = watcher.watchFieldWhenValid({
      field: 'username',
      callback: (value) => {
        // This will only execute when the form is valid
        console.log('Valid username change:', value);
      },
    });
    
    return unsubscribe;
  }, [watcher]);
  
  // Watch for field errors
  useEffect(() => {
    const unsubscribe = watcher.watchFieldErrors('password', (hasError, errorMessage) => {
      if (hasError) {
        console.log('Password error:', errorMessage);
      } else {
        console.log('Password is valid');
      }
    });
    
    return unsubscribe;
  }, [watcher]);
  
  return (
    <form>{/* Form fields */}</form>
  );
};
```

## Auto-Save Implementation

Create an auto-save feature with the watcher:

```tsx
import { useReform, useReformWatcher } from '@reform/core';

const AutoSaveForm = () => {
  const form = useReform<UserForm>(config);
  const watcher = useReformWatcher(form);
  const [lastSaved, setLastSaved] = useState<Date | null>(null);
  const [autoSaveEnabled, setAutoSaveEnabled] = useState(true);
  
  // Set up auto-save
  useEffect(() => {
    const saveForm = async () => {
      try {
        await saveFormToServer(form.getGroups());
        setLastSaved(new Date());
      } catch (error) {
        console.error('Auto-save failed:', error);
      }
    };
    
    // Create auto-save controller
    const autoSave = watcher.createAutoSave(saveForm, 2000);
    
    // Clean up
    return () => {
      autoSave.unsubscribe();
    };
  }, [watcher, form]);
  
  // Toggle auto-save
  const toggleAutoSave = () => {
    if (autoSaveEnabled) {
      autoSave.stop();
    } else {
      autoSave.start();
    }
    setAutoSaveEnabled(!autoSaveEnabled);
  };
  
  // Manually trigger save
  const handleManualSave = () => {
    autoSave.trigger();
  };
  
  return (
    <div>
      <form>{/* Form fields */}</form>
      
      <div className="auto-save-controls">
        <label>
          <input
            type="checkbox"
            checked={autoSaveEnabled}
            onChange={toggleAutoSave}
          />
          Enable auto-save
        </label>
        
        <button onClick={handleManualSave}>
          Save Now
        </button>
        
        {lastSaved && (
          <div className="last-saved">
            Last saved: {lastSaved.toLocaleString()}
          </div>
        )}
      </div>
    </div>
  );
};
```

## API Reference

### useReformWatcher

```typescript
function useReformWatcher<T extends Record<string, any>>(
  reform: ReformReturn<T>,
  options: FormStateObserverOptions<T> = {}
): ReformWatcherReturn<T>
```

#### Options

| Option | Type | Description |
|--------|------|-------------|
| onChange | `FormStateChangeHandler<T>` | Called on any form state change |
| onDirtyChange | `(isDirty: boolean) => void` | Called when dirty state changes |
| onErrorsChange | `(hasErrors: boolean, errors: any) => void` | Called when errors change |
| onSubmitCountChange | `(count: number) => void` | Called when submit count changes |
| onTouchedChange | `(hasTouched: boolean, touched: any) => void` | Called when touched fields change |
| onValidChange | `(isValid: boolean) => void` | Called when validity changes |
| onSubmittingChange | `(isSubmitting: boolean) => void` | Called when submitting state changes |

### Field Watching Methods

| Method | Description |
|--------|-------------|
| watchField | Watch a specific field for changes |
| watchFields | Watch multiple fields at once |
| getFieldValue | Get the current value of a field |
| triggerFieldWatch | Manually trigger field watchers |

### Form State Methods

| Method | Description |
|--------|-------------|
| subscribeToState | Subscribe to form state changes |
| getFormState | Get current form state |
| getPreviousState | Get previous form state |
| isDirty | Check if form is dirty |
| hasErrors | Check if form has errors |
| isSubmitting | Check if form is submitting |
| isValid | Check if form is valid |

### Combined Methods

| Method | Description |
|--------|-------------|
| watchFieldWithState | Watch a field and form state together |
| watchFieldWhenValid | Watch a field only when form is valid |
| watchFieldErrors | Watch for errors in a specific field |
| createAutoSave | Create an auto-save controller |

### FieldWatchConfig

```typescript
interface FieldWatchConfig<T, K extends keyof T> {
  field: K;
  callback: (
    value: T[K],
    previousValue: T[K],
    groupIndex: number,
    formData: { groups: FormGroup<T>[] }
  ) => void;
  groupIndex?: number;
  debounce?: number;
  immediate?: boolean;
}
```

### AutoSaveController

```typescript
interface AutoSaveController {
  stop: () => void;
  start: () => void;
  trigger: () => void;
  unsubscribe: () => void;
}
```