Skip to main content

Autosave with React Hook Form Controlled Inputs

React Hook Form's controlled inputs require special handling for autosave. This guide shows you how to integrate Form Guardian properly.

The Challenge

React Hook Form can work in both controlled and uncontrolled modes. When using controlled inputs, you need to restore values using setValue rather than direct DOM manipulation.

Basic Setup

import { useForm } from 'react-hook-form';
import { useFormAutosave } from '@form-guardian/react';
import { useEffect } from 'react';

interface FormData {
name: string;
email: string;
message: string;
}

function ControlledForm() {
const { register, handleSubmit, setValue, getValues, watch } = useForm<FormData>({
defaultValues: {
name: '',
email: '',
message: '',
},
});

const { formRef, restoreValues, clearDraft } = useFormAutosave('controlled-form', {
autoRestore: false, // We'll restore manually for controlled inputs
});

// Restore draft on mount
useEffect(() => {
restoreValues(
(field, value) => {
setValue(field as keyof FormData, value, { shouldValidate: false });
},
() => getValues()
);
}, []);

const onSubmit = async (data: FormData) => {
// Submit logic
await clearDraft();
};

return (
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<input {...register('name', { required: true })} />
<input {...register('email', { required: true })} type="email" />
<textarea {...register('message', { required: true })} />
<button type="submit">Submit</button>
</form>
);
}

Key Points

1. Use shouldValidate: false When Restoring

setValue(field, value, { shouldValidate: false });

This prevents validation errors from triggering when restoring draft values.

2. Manual Restoration

autoRestore: false, // Disable auto-restore

Controlled inputs need manual restoration using restoreValues to properly update React state.

3. Get Current Values

restoreValues(
(field, value) => setValue(field, value),
() => getValues() // Provide current values
);

The second parameter provides current form values for Form Guardian to track.

Advanced: With Validation

function ControlledFormWithValidation() {
const {
register,
handleSubmit,
setValue,
getValues,
formState: { errors, isDirty }
} = useForm<FormData>({
defaultValues: {
name: '',
email: '',
message: '',
},
});

const { formRef, restoreValues, clearDraft, hasDraft } = useFormAutosave(
'controlled-form',
{ autoRestore: false }
);

useEffect(() => {
if (hasDraft) {
restoreValues(
(field, value) => {
setValue(field as keyof FormData, value, {
shouldValidate: false,
shouldDirty: true, // Mark as dirty after restore
});
},
() => getValues()
);
}
}, [hasDraft]);

const onSubmit = async (data: FormData) => {
await submitForm(data);
await clearDraft();
};

return (
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<div>
<input
{...register('name', {
required: 'Name is required',
minLength: { value: 2, message: 'Min 2 characters' }
})}
/>
{errors.name && <span>{errors.name.message}</span>}
</div>

<div>
<input
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email'
}
})}
type="email"
/>
{errors.email && <span>{errors.email.message}</span>}
</div>

<button type="submit">Submit</button>
</form>
);
}

With Watch Mode

If you're using watch for real-time updates:

function ControlledFormWithWatch() {
const { register, handleSubmit, setValue, getValues, watch } = useForm<FormData>();

const { formRef, restoreValues, clearDraft } = useFormAutosave(
'controlled-form',
{ autoRestore: false }
);

const watchedValues = watch(); // Watch all fields

useEffect(() => {
restoreValues(
(field, value) => setValue(field as keyof FormData, value),
() => getValues()
);
}, []);

// Watch values are automatically tracked by Form Guardian
return (
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
<input {...register('email')} type="email" />
</form>
);
}

Best Practices

  1. Always use shouldValidate: false when restoring to avoid validation errors
  2. Set shouldDirty: true if you want restored values to mark form as dirty
  3. Disable auto-restore for controlled inputs and restore manually
  4. Clear draft after successful submission to prevent stale data
  5. Handle errors gracefully when restoration fails

Common Pitfalls

❌ Don't Use autoRestore: true with Controlled Inputs

// ❌ Wrong - won't work properly with controlled inputs
const { formRef } = useFormAutosave('form', {
autoRestore: true, // This tries to set DOM values directly
});

✅ Do Use Manual Restoration

// ✅ Correct - manual restoration for controlled inputs
const { formRef, restoreValues } = useFormAutosave('form', {
autoRestore: false,
});

useEffect(() => {
restoreValues(setValue, getValues);
}, []);

Next Steps