@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
Markdown
# 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>;
}
```