Skip to main content

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: false for controlled forms
  • Call restoreValues(setValue, getValues) on mount
  • Watch for changes and call saveValues()
  • Clear draft after successful submission
  • Use batchSaveInterval for better performance

❌ DON'T

  • Don't use autoRestore: true with 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
});