React Hook Form Integration
Complete guide for integrating Form Guardian with React Hook Form. Combines powerful validation with automatic draft saving.
🚀 Quick Start
Installation
npm install @form-guardian/react react-hook-form
Basic Integration
import { useForm } from 'react-hook-form';
import { useFormAutosave } from '@form-guardian/react';
import { useEffect } from 'react';
function MyForm() {
const { register, handleSubmit, setValue, getValues, watch } = useForm();
const { formRef, restoreValues, clearDraft, saveValues } = useFormAutosave(
'my-form',
{ autoRestore: false } // Manual restore for controlled forms
);
// Restore draft on mount
useEffect(() => {
restoreValues(setValue, getValues);
}, []);
// Auto-save on field changes
useEffect(() => {
const subscription = watch(() => {
saveValues();
});
return () => subscription.unsubscribe();
}, [watch]);
const onSubmit = async (data) => {
await clearDraft();
console.log(data);
};
return (
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<input {...register('name', { required: true })} />
<input {...register('email', { required: true })} />
<button type="submit">Submit</button>
</form>
);
}
📋 Step-by-Step Guide
1. Setup Form with React Hook Form
import { useForm } from 'react-hook-form';
function MyForm() {
const {
register,
handleSubmit,
setValue,
getValues,
watch,
formState: { errors },
} = useForm({
defaultValues: {
name: '',
email: '',
message: '',
},
});
// ... rest of component
}
2. Add Form Guardian Hook
import { useFormAutosave } from '@form-guardian/react';
const { formRef, restoreValues, clearDraft, saveValues, hasDraft } =
useFormAutosave('contact-form', {
autoRestore: false, // Important for controlled forms!
debounceMs: 300,
onAfterSave: () => {
console.log('Draft saved');
},
});
3. Restore Draft on Mount
useEffect(() => {
// Restore saved draft to React Hook Form state
restoreValues(setValue, getValues);
}, []); // Run once on mount
4. Auto-Save on Changes
useEffect(() => {
// Watch for changes and save
const subscription = watch(() => {
saveValues();
});
return () => subscription.unsubscribe();
}, [watch]);
5. Clear Draft on Submit
const onSubmit = async (data) => {
// Clear draft before/after submission
await clearDraft();
// Submit data to server
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data),
});
};
🎯 Complete Example with Validation
import { useForm } from 'react-hook-form';
import { useFormAutosave } from '@form-guardian/react';
import { useEffect } from 'react';
interface FormData {
name: string;
email: string;
age: number;
message: string;
}
function RegistrationForm() {
const {
register,
handleSubmit,
setValue,
getValues,
watch,
formState: { errors, isSubmitting },
} = useForm<FormData>({
mode: 'onBlur',
});
const { formRef, restoreValues, clearDraft, saveValues, hasDraft, draftTimestamp } =
useFormAutosave<FormData>('registration-form', {
autoRestore: false,
debounceMs: 500,
batchSaveInterval: 5000, // Batch saves every 5 seconds
onAfterSave: (values) => {
console.log('Draft saved:', values);
},
});
// Restore draft on mount
useEffect(() => {
restoreValues(setValue, getValues);
}, []);
// Auto-save on changes
useEffect(() => {
const subscription = watch((data) => {
console.log('Form changed, saving...', data);
saveValues();
});
return () => subscription.unsubscribe();
}, [watch]);
const onSubmit = async (data: FormData) => {
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (response.ok) {
// Clear draft after successful submission
await clearDraft();
alert('Registration successful!');
}
} catch (error) {
console.error('Submission failed:', error);
}
};
return (
<div>
{hasDraft && (
<div className="draft-banner">
📝 Draft saved at {new Date(draftTimestamp).toLocaleString()}
</div>
)}
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="name">Name *</label>
<input
id="name"
{...register('name', {
required: 'Name is required',
minLength: { value: 2, message: 'Min 2 characters' },
})}
/>
{errors.name && <span className="error">{errors.name.message}</span>}
</div>
<div>
<label htmlFor="email">Email *</label>
<input
id="email"
type="email"
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email',
},
})}
/>
{errors.email && <span className="error">{errors.email.message}</span>}
</div>
<div>
<label htmlFor="age">Age</label>
<input
id="age"
type="number"
{...register('age', {
min: { value: 18, message: 'Must be 18+' },
max: { value: 120, message: 'Invalid age' },
})}
/>
{errors.age && <span className="error">{errors.age.message}</span>}
</div>
<div>
<label htmlFor="message">Message</label>
<textarea
id="message"
{...register('message', {
maxLength: { value: 500, message: 'Max 500 characters' },
})}
/>
{errors.message && <span className="error">{errors.message.message}</span>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
</div>
);
}
🔧 Advanced Patterns
Field-Level Autosave
Save only when specific fields change:
const { watch } = useForm();
const { saveValues } = useFormAutosave('my-form', { autoRestore: false });
useEffect(() => {
const subscription = watch((value, { name }) => {
// Save only on important fields
if (['name', 'email', 'message'].includes(name)) {
saveValues();
}
});
return () => subscription.unsubscribe();
}, [watch]);
Conditional Autosave
Save drafts only when form is dirty:
const { watch, formState } = useForm();
const { saveValues } = useFormAutosave('my-form', { autoRestore: false });
useEffect(() => {
if (formState.isDirty) {
const subscription = watch(() => saveValues());
return () => subscription.unsubscribe();
}
}, [watch, formState.isDirty]);
Custom Restore Logic
const { formRef, getDraftValues, clearDraft } = useFormAutosave('my-form', {
autoRestore: false,
});
useEffect(() => {
const restoreDraft = async () => {
const draft = await getDraftValues();
if (!draft) return;
// Show confirmation dialog
const shouldRestore = window.confirm(
`Found draft from ${new Date(draft.updatedAt).toLocaleString()}. Restore?`
);
if (shouldRestore) {
// Restore to React Hook Form
Object.entries(draft.values).forEach(([key, value]) => {
setValue(key, value);
});
} else {
// User declined, clear draft
await clearDraft();
}
};
restoreDraft();
}, []);
💡 Best Practices
✅ DO
- Use
autoRestore: falsefor controlled forms - Call
restoreValues(setValue, getValues)on mount - Watch for changes and call
saveValues() - Clear draft after successful submission
- Use
batchSaveIntervalfor better performance
❌ DON'T
- Don't use
autoRestore: truewith React Hook Form - Don't forget to clear draft after submission
- Don't save on every keystroke without debounce
- Don't save password fields (already excluded by default)
📊 Performance Optimization
Debouncing
const { formRef } = useFormAutosave('my-form', {
debounceMs: 500, // Wait 500ms after last change
});
Batching
const { formRef } = useFormAutosave('my-form', {
batchSaveInterval: 5000, // Save accumulated changes every 5s
debounceMs: 300,
});
Selective Fields
const { formRef } = useFormAutosave('my-form', {
whitelist: ['input[name="title"]', 'textarea[name="content"]'], // Only save these
});