UNPKG

@matthew.ngo/reform

Version:

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

375 lines (308 loc) 10.6 kB
# Reform Form Persistence Reform provides a powerful form persistence system that allows you to save and restore form state across sessions. This document covers the form persistence capabilities of Reform. ## Table of Contents - [Overview](#overview) - [Basic Usage](#basic-usage) - [Auto-Save](#auto-save) - [Custom Storage Providers](#custom-storage-providers) - [Callbacks and Metadata](#callbacks-and-metadata) - [API Reference](#api-reference) ## Overview Reform's form persistence system allows you to: 1. **Save form state** to localStorage or a custom storage provider 2. **Restore form state** when the user returns to the form 3. **Auto-save** form changes at regular intervals 4. **Add metadata** to saved form state for additional context ## Basic Usage Here's a basic example of using form persistence: ```tsx import { useReform, useReformPersistence } from '@reform/core'; type UserForm = { name: string; email: string; preferences: { theme: string; notifications: boolean; }; }; const UserFormComponent = () => { const form = useReform<UserForm>({ defaultData: { name: '', email: '', preferences: { theme: 'light', notifications: true, }, }, minGroups: 1, }); // Set up form persistence const persistence = useReformPersistence(form, { enabled: true, storageKey: 'user-form-state', }); // Manual save and restore const handleSave = async () => { await persistence.saveState(); alert('Form state saved!'); }; const handleRestore = async () => { await persistence.loadState(); alert('Form state restored!'); }; const handleClear = async () => { await persistence.clearState(); alert('Saved form state cleared!'); }; return ( <div> <form> <div> <label>Name</label> <input {...form.register(0, 'name')} /> </div> <div> <label>Email</label> <input {...form.register(0, 'email')} /> </div> <div> <label>Theme</label> <select {...form.register(0, 'preferences.theme')}> <option value="light">Light</option> <option value="dark">Dark</option> <option value="system">System</option> </select> </div> <div> <label> <input type="checkbox" {...form.register(0, 'preferences.notifications')} /> Enable notifications </label> </div> </form> <div className="persistence-controls"> <button onClick={handleSave}>Save Form State</button> <button onClick={handleRestore}>Restore Form State</button> <button onClick={handleClear}>Clear Saved State</button> {persistence.lastSaved && ( <div className="last-saved"> Last saved: {new Date(persistence.lastSaved).toLocaleString()} </div> )} </div> </div> ); }; ``` ## Auto-Save Reform can automatically save form state at regular intervals or when the form changes: ```tsx import { useReform, useReformPersistence } from '@reform/core'; const AutoSaveForm = () => { const form = useReform<UserForm>(config); // Set up auto-save const persistence = useReformPersistence(form, { enabled: true, storageKey: 'auto-save-form', autoSave: true, autoSaveInterval: 10000, // Save every 10 seconds debounceAutoSave: true, // Debounce saves when form changes debounceDelay: 1000, // Wait 1 second after changes before saving }); // Toggle auto-save const toggleAutoSave = () => { persistence.setAutoSave(!persistence.isAutoSaveEnabled); }; return ( <div> <form>{/* Form fields */}</form> <div className="auto-save-controls"> <label> <input type="checkbox" checked={persistence.isAutoSaveEnabled} onChange={toggleAutoSave} /> Enable auto-save </label> <button onClick={persistence.resetAutoSaveTimer}> Reset Auto-Save Timer </button> {persistence.lastSaved && ( <div className="last-saved"> Last auto-saved: {new Date(persistence.lastSaved).toLocaleString()} </div> )} </div> </div> ); }; ``` ## Custom Storage Providers You can create custom storage providers to save form state to different locations: ```tsx import { useReform, useReformPersistence, StorageProvider } from '@reform/core'; // Create a custom storage provider that uses IndexedDB class IndexedDBStorageProvider<T> implements StorageProvider<T> { private dbName: string; private storeName: string; constructor(dbName: string = 'reformForms', storeName: string = 'formState') { this.dbName = dbName; this.storeName = storeName; } private async getDB(): Promise<IDBDatabase> { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, 1); request.onupgradeneeded = () => { const db = request.result; if (!db.objectStoreNames.contains(this.storeName)) { db.createObjectStore(this.storeName); } }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } async save(key: string, data: any): Promise<void> { const db = await this.getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(this.storeName, 'readwrite'); const store = transaction.objectStore(this.storeName); const request = store.put(data, key); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } async load(key: string): Promise<any> { const db = await this.getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(this.storeName, 'readonly'); const store = transaction.objectStore(this.storeName); const request = store.get(key); request.onsuccess = () => resolve(request.result || null); request.onerror = () => reject(request.error); }); } async remove(key: string): Promise<void> { const db = await this.getDB(); return new Promise((resolve, reject) => { const transaction = db.transaction(this.storeName, 'readwrite'); const store = transaction.objectStore(this.storeName); const request = store.delete(key); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } } // Use the custom storage provider const MyForm = () => { const form = useReform<UserForm>(config); const persistence = useReformPersistence(form, { enabled: true, storageKey: 'indexed-db-form', storageProvider: new IndexedDBStorageProvider(), }); return ( <div>{/* Form fields */}</div> ); }; ``` ## Callbacks and Metadata Reform allows you to add callbacks and metadata to form persistence: ```tsx import { useReform, useReformPersistence } from '@reform/core'; import { useAuth } from './auth'; const FormWithMetadata = () => { const form = useReform<UserForm>(config); const { user } = useAuth(); const persistence = useReformPersistence(form, { enabled: true, storageKey: `user-form-${user.id}`, // Add metadata to saved form state metadata: { userId: user.id, formVersion: '1.2.0', lastModifiedBy: user.email, }, // Or use a function to get dynamic metadata // metadata: () => ({ // userId: user.id, // timestamp: new Date().toISOString(), // browser: navigator.userAgent, // }), // Callbacks onBeforeSave: (groups) => { // Return false to prevent saving if (groups.some(group => !group.data.name)) { alert('Please fill in all name fields before saving'); return false; } return true; }, onAfterSave: (groups) => { console.log('Form saved successfully', groups); }, onAfterRestore: (groups) => { console.log('Form restored successfully', groups); }, }); return ( <div>{/* Form fields */}</div> ); }; ``` ## API Reference ### useReformPersistence ```typescript function useReformPersistence<T extends Record<string, any>>( reform: ReformReturn<T>, config: FormPersistenceConfig<T> = {} ): FormPersistenceReturn<T> ``` #### Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| | enabled | `boolean` | `false` | Enable/disable persistence | | storageKey | `string` | `"reform-form-state"` | Key for storing form state | | storageProvider | `StorageProvider<T>` | `localStorage` | Storage provider | | autoSave | `boolean` | `false` | Enable auto-saving | | autoSaveInterval | `number` | `5000` | Auto-save interval in ms | | debounceAutoSave | `boolean` | `true` | Debounce auto-save | | debounceDelay | `number` | `500` | Debounce delay in ms | | onBeforeSave | `(groups: FormGroup<T>[]) => boolean \| Promise<boolean>` | `undefined` | Hook before saving | | onAfterSave | `(groups: FormGroup<T>[]) => void` | `undefined` | Hook after saving | | onAfterRestore | `(groups: FormGroup<T>[]) => void` | `undefined` | Hook after restoring | | metadata | `Record<string, any> \| () => Record<string, any>` | `undefined` | Additional metadata | #### Return Value | Property/Method | Type | Description | |----------------|------|-------------| | isSaving | `boolean` | Whether form is currently saving | | isLoading | `boolean` | Whether form is currently loading | | lastSaved | `number \| null` | Timestamp of last save | | saveState | `() => Promise<void>` | Save form state manually | | loadState | `() => Promise<void>` | Load form state manually | | clearState | `() => Promise<void>` | Clear saved form state | | resetAutoSaveTimer | `() => void` | Reset auto-save timer | | setAutoSave | `(enabled: boolean) => void` | Enable/disable auto-save | | isAutoSaveEnabled | `boolean` | Whether auto-save is enabled | ### StorageProvider ```typescript interface StorageProvider<T> { save: (key: string, data: FormPersistenceData<T>) => Promise<void>; load: (key: string) => Promise<FormPersistenceData<T> | null>; remove: (key: string) => Promise<void>; } ``` ### FormPersistenceData ```typescript interface FormPersistenceData<T> { groups: FormGroup<T>[]; timestamp: number; version: string; metadata?: Record<string, any>; } ```