Formik Integration
Complete guide for integrating Form Guardian with Formik. Combines Formik's validation with automatic draft saving.
🚀 Quick Start
Installation
npm install @form-guardian/react formik
Basic Integration
import { useFormik } from 'formik';
import { useFormAutosave } from '@form-guardian/react';
import { useEffect } from 'react';
function MyForm() {
const formik = useFormik({
initialValues: {
name: '',
email: '',
message: '',
},
onSubmit: async (values) => {
await autosave.clear();
console.log(values);
},
});
const { formRef, restoreValues, clearDraft, saveValues } = useFormAutosave(
'my-form',
{ autoRestore: false } // Manual restore for controlled forms
);
// Restore draft on mount
useEffect(() => {
restoreValues(formik.setFieldValue, formik.values);
}, []);
// Auto-save on field changes
useEffect(() => {
if (formik.dirty) {
saveValues();
}
}, [formik.values]);
return (
<form ref={formRef} onSubmit={formik.handleSubmit}>
<input
name="name"
value={formik.values.name}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
<input
name="email"
type="email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
<textarea
name="message"
value={formik.values.message}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
<button type="submit">Submit</button>
</form>
);
}
📋 Complete Example with Validation
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { useFormAutosave } from '@form-guardian/react';
import { useEffect } from 'react';
const validationSchema = Yup.object({
name: Yup.string()
.min(2, 'Must be at least 2 characters')
.required('Required'),
email: Yup.string()
.email('Invalid email address')
.required('Required'),
message: Yup.string()
.max(500, 'Must be 500 characters or less')
.required('Required'),
});
function ContactForm() {
const formik = useFormik({
initialValues: {
name: '',
email: '',
message: '',
},
validationSchema,
onSubmit: async (values) => {
await clearDraft();
// Submit to API
await fetch('/api/contact', {
method: 'POST',
body: JSON.stringify(values),
});
},
});
const { formRef, restoreValues, clearDraft, saveValues, hasDraft, draftTimestamp } =
useFormAutosave('contact-form', {
autoRestore: false,
debounceMs: 500,
onAfterSave: () => {
console.log('Draft saved');
},
});
// Restore on mount
useEffect(() => {
restoreValues(formik.setFieldValue, formik.values);
}, []);
// Auto-save on changes
useEffect(() => {
if (formik.dirty) {
saveValues();
}
}, [formik.values]);
return (
<div>
{hasDraft && (
<div className="draft-alert">
📝 Draft saved at {new Date(draftTimestamp).toLocaleString()}
</div>
)}
<form ref={formRef} onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="name">Name *</label>
<input
id="name"
name="name"
type="text"
value={formik.values.name}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.name && formik.errors.name && (
<div className="error">{formik.errors.name}</div>
)}
</div>
<div>
<label htmlFor="email">Email *</label>
<input
id="email"
name="email"
type="email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.email && formik.errors.email && (
<div className="error">{formik.errors.email}</div>
)}
</div>
<div>
<label htmlFor="message">Message *</label>
<textarea
id="message"
name="message"
value={formik.values.message}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.message && formik.errors.message && (
<div className="error">{formik.errors.message}</div>
)}
</div>
<button type="submit" disabled={formik.isSubmitting}>
{formik.isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
</div>
);
}
🔧 Advanced Patterns
Field-Level Autosave
Save only when specific fields change:
const [lastSavedValues, setLastSavedValues] = useState(formik.initialValues);
useEffect(() => {
const changedFields = Object.keys(formik.values).filter(
key => formik.values[key] !== lastSavedValues[key]
);
if (changedFields.length > 0 && formik.dirty) {
saveValues();
setLastSavedValues(formik.values);
}
}, [formik.values]);
Conditional Autosave with Validation
Save drafts only when form passes validation:
useEffect(() => {
const saveDraft = async () => {
// Validate silently
const errors = await formik.validateForm();
// Save only if no errors or user has interacted
if (Object.keys(errors).length === 0 || formik.dirty) {
saveValues();
}
};
if (formik.dirty) {
saveDraft();
}
}, [formik.values]);
Custom Restore with Confirmation
const { formRef, getDraftValues, clearDraft } = useFormAutosave('my-form', {
autoRestore: false,
});
useEffect(() => {
const handleRestore = async () => {
const draft = await getDraftValues();
if (!draft) return;
const shouldRestore = window.confirm(
`Found draft from ${new Date(draft.updatedAt).toLocaleString()}. Restore?`
);
if (shouldRestore) {
// Restore to Formik
Object.entries(draft.values).forEach(([key, value]) => {
formik.setFieldValue(key, value);
});
} else {
await clearDraft();
}
};
handleRestore();
}, []);
💡 Best Practices
✅ DO
- Use
autoRestore: falsefor controlled forms - Call
restoreValues(formik.setFieldValue, formik.values)on mount - Save on
formik.valueschanges whenformik.dirtyis true - Clear draft after successful submission
- Use debouncing for better performance
❌ DON'T
- Don't use
autoRestore: truewith Formik - 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 every 5 seconds
debounceMs: 300,
});