Skip to main content

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: false for controlled forms
  • Call restoreValues(formik.setFieldValue, formik.values) on mount
  • Save on formik.values changes when formik.dirty is true
  • Clear draft after successful submission
  • Use debouncing for better performance

❌ DON'T

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