Autosave Multi-Step Forms
Multi-step forms are common in applications, and preserving user progress across steps is crucial. This guide shows you how to implement autosave for multi-step forms.
Basic Multi-Step Form
import { useState } from 'react';
import { useFormAutosave } from '@form-guardian/react';
interface FormData {
step1: { name: string; email: string };
step2: { address: string; city: string };
step3: { payment: string; terms: boolean };
}
function MultiStepForm() {
const [step, setStep] = useState(1);
const { formRef, clearDraft } = useFormAutosave('multi-step-form', {
autoRestore: true,
ttl: { days: 7 }, // Keep draft for 7 days
});
const handleNext = () => {
if (step < 3) {
setStep(step + 1);
}
};
const handlePrev = () => {
if (step > 1) {
setStep(step - 1);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Submit all steps
await clearDraft();
// Navigate to success page
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
{step === 1 && (
<div>
<h2>Step 1: Personal Information</h2>
<input name="step1.name" placeholder="Name" />
<input name="step1.email" type="email" placeholder="Email" />
<button type="button" onClick={handleNext}>Next</button>
</div>
)}
{step === 2 && (
<div>
<h2>Step 2: Address</h2>
<input name="step2.address" placeholder="Address" />
<input name="step2.city" placeholder="City" />
<button type="button" onClick={handlePrev}>Previous</button>
<button type="button" onClick={handleNext}>Next</button>
</div>
)}
{step === 3 && (
<div>
<h2>Step 3: Payment</h2>
<input name="step3.payment" placeholder="Payment method" />
<label>
<input name="step3.terms" type="checkbox" />
Accept terms
</label>
<button type="button" onClick={handlePrev}>Previous</button>
<button type="submit">Submit</button>
</div>
)}
</form>
);
}
With React Hook Form
For more complex validation:
import { useForm } from 'react-hook-form';
import { useFormAutosave } from '@form-guardian/react';
import { useState, useEffect } from 'react';
function MultiStepFormWithRHF() {
const [step, setStep] = useState(1);
const { register, handleSubmit, setValue, getValues, trigger } = useForm({
defaultValues: {
name: '',
email: '',
address: '',
city: '',
payment: '',
terms: false,
},
});
const { formRef, restoreValues, clearDraft } = useFormAutosave(
'multi-step-form',
{ autoRestore: false }
);
// Restore draft on mount
useEffect(() => {
restoreValues(
(field, value) => setValue(field, value, { shouldValidate: false }),
() => getValues()
);
}, []);
const handleNext = async () => {
// Validate current step before proceeding
const fields = step === 1
? ['name', 'email']
: step === 2
? ['address', 'city']
: ['payment', 'terms'];
const isValid = await trigger(fields);
if (isValid && step < 3) {
setStep(step + 1);
}
};
const handlePrev = () => {
if (step > 1) {
setStep(step - 1);
}
};
const onSubmit = async (data: any) => {
await submitForm(data);
await clearDraft();
};
return (
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
{step === 1 && (
<div>
<h2>Step 1: Personal Information</h2>
<input {...register('name', { required: true })} />
<input {...register('email', { required: true })} type="email" />
<button type="button" onClick={handleNext}>Next</button>
</div>
)}
{step === 2 && (
<div>
<h2>Step 2: Address</h2>
<input {...register('address', { required: true })} />
<input {...register('city', { required: true })} />
<button type="button" onClick={handlePrev}>Previous</button>
<button type="button" onClick={handleNext}>Next</button>
</div>
)}
{step === 3 && (
<div>
<h2>Step 3: Payment</h2>
<input {...register('payment', { required: true })} />
<label>
<input {...register('terms', { required: true })} type="checkbox" />
Accept terms
</label>
<button type="button" onClick={handlePrev}>Previous</button>
<button type="submit">Submit</button>
</div>
)}
</form>
);
}
Show Progress Indicator
function MultiStepFormWithProgress() {
const [step, setStep] = useState(1);
const totalSteps = 3;
const { formRef, hasDraft, clearDraft } = useFormAutosave('multi-step-form', {
autoRestore: true,
});
return (
<div>
{/* Progress indicator */}
<div className="progress-bar">
{Array.from({ length: totalSteps }).map((_, i) => (
<div
key={i}
className={i + 1 <= step ? 'completed' : 'pending'}
>
Step {i + 1}
</div>
))}
</div>
{/* Draft indicator */}
{hasDraft && (
<div className="draft-notice">
You have a saved draft. Your progress will be restored.
</div>
)}
<form ref={formRef}>
{/* Form steps */}
</form>
</div>
);
}
Per-Step Autosave
If you want separate drafts for each step:
function MultiStepFormPerStep() {
const [step, setStep] = useState(1);
// Separate autosave for each step
const step1 = useFormAutosave('multi-step-form-step1', { autoRestore: true });
const step2 = useFormAutosave('multi-step-form-step2', { autoRestore: true });
const step3 = useFormAutosave('multi-step-form-step3', { autoRestore: true });
const currentAutosave = step === 1 ? step1 : step === 2 ? step2 : step3;
const handleSubmit = async () => {
// Clear all step drafts
await step1.clearDraft();
await step2.clearDraft();
await step3.clearDraft();
// Submit
};
return (
<form ref={currentAutosave.formRef}>
{/* Form content */}
</form>
);
}
Best Practices
- Use appropriate TTL - Multi-step forms often take longer, use longer TTL
- Show progress - Let users know which step they're on
- Validate before proceeding - Don't let users skip invalid steps
- Clear all drafts on completion - Clean up after successful submission
- Handle navigation - Save draft when user navigates away
Common Pitfalls
❌ Don't Forget to Clear Drafts
// ❌ Wrong - draft persists after submission
const onSubmit = async () => {
await submitForm();
// Forgot to clear draft!
};
✅ Clear Drafts After Submission
// ✅ Correct - clear draft after successful submission
const onSubmit = async () => {
await submitForm();
await clearDraft(); // Clear draft
};
Next Steps
- 📖 Getting Started - Basic setup
- 🍳 Dynamic Fields - Handle dynamic form fields
- 📚 API Reference - Complete API documentation