UNPKG

@matthew.ngo/reform

Version:

A flexible and powerful React form management library with advanced validation, state observation, and multi-group support

385 lines (322 loc) 10.3 kB
# 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; } ```