Перейти к основному содержимому

Интеграция с React Hook Form

Полное руководство по интеграции Form Guardian с React Hook Form. Объединяет мощную валидацию с автоматическим сохранением черновиков.

🚀 Быстрый старт

Установка

npm install @form-guardian/react react-hook-form

Базовая интеграция

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 } // Ручное восстановление для контролируемых форм
);

// Восстановление черновика при монтировании
useEffect(() => {
restoreValues(setValue, getValues);
}, []);

// Автосохранение при изменении полей
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">Отправить</button>
</form>
);
}

📋 Пошаговое руководство

1. Настройка формы с React Hook Form

import { useForm } from 'react-hook-form';

function MyForm() {
const {
register,
handleSubmit,
setValue,
getValues,
watch,
formState: { errors },
} = useForm({
defaultValues: {
name: '',
email: '',
message: '',
},
});

// ... остальная часть компонента
}

2. Добавление хука Form Guardian

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

const { formRef, restoreValues, clearDraft, saveValues, hasDraft } =
useFormAutosave('contact-form', {
autoRestore: false, // Важно для контролируемых форм!
debounceMs: 300,
onAfterSave: () => {
console.log('Черновик сохранен');
},
});

3. Восстановление черновика при монтировании

useEffect(() => {
// Восстановление сохраненного черновика в состояние React Hook Form
restoreValues(setValue, getValues);
}, []); // Запускается один раз при монтировании

4. Автосохранение при изменениях

useEffect(() => {
// Отслеживание изменений и сохранение
const subscription = watch(() => {
saveValues();
});

return () => subscription.unsubscribe();
}, [watch]);

5. Очистка черновика при отправке

const onSubmit = async (data) => {
// Очистка черновика до/после отправки
await clearDraft();

// Отправка данных на сервер
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data),
});
};

🎯 Полный пример с валидацией

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, // Пакетное сохранение каждые 5 секунд
onAfterSave: (values) => {
console.log('Черновик сохранен:', values);
},
});

// Восстановление черновика при монтировании
useEffect(() => {
restoreValues(setValue, getValues);
}, []);

// Автосохранение при изменениях
useEffect(() => {
const subscription = watch((data) => {
console.log('Форма изменилась, сохранение...', 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) {
// Очистка черновика после успешной отправки
await clearDraft();
alert('Регистрация успешна!');
}
} catch (error) {
console.error('Ошибка отправки:', error);
}
};

return (
<div>
{hasDraft && (
<div className="draft-banner">
📝 Черновик сохранен в {new Date(draftTimestamp).toLocaleString()}
</div>
)}

<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="name">Имя *</label>
<input
id="name"
{...register('name', {
required: 'Имя обязательно',
minLength: { value: 2, message: 'Минимум 2 символа' },
})}
/>
{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 обязателен',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Неверный email',
},
})}
/>
{errors.email && <span className="error">{errors.email.message}</span>}
</div>

<div>
<label htmlFor="age">Возраст</label>
<input
id="age"
type="number"
{...register('age', {
min: { value: 18, message: 'Должно быть 18+' },
max: { value: 120, message: 'Неверный возраст' },
})}
/>
{errors.age && <span className="error">{errors.age.message}</span>}
</div>

<div>
<label htmlFor="message">Сообщение</label>
<textarea
id="message"
{...register('message', {
maxLength: { value: 500, message: 'Максимум 500 символов' },
})}
/>
{errors.message && <span className="error">{errors.message.message}</span>}
</div>

<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Отправка...' : 'Отправить'}
</button>
</form>
</div>
);
}

🔧 Продвинутые паттерны

Автосохранение на уровне полей

Сохранение только при изменении конкретных полей:

const { watch } = useForm();
const { saveValues } = useFormAutosave('my-form', { autoRestore: false });

useEffect(() => {
const subscription = watch((value, { name }) => {
// Сохранять только для важных полей
if (['name', 'email', 'message'].includes(name)) {
saveValues();
}
});
return () => subscription.unsubscribe();
}, [watch]);

Условное автосохранение

Сохранять черновики только когда форма изменена:

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]);

Кастомная логика восстановления

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

useEffect(() => {
const restoreDraft = async () => {
const draft = await getDraftValues();
if (!draft) return;

// Показать диалог подтверждения
const shouldRestore = window.confirm(
`Найден черновик от ${new Date(draft.updatedAt).toLocaleString()}. Восстановить?`
);

if (shouldRestore) {
// Восстановление в React Hook Form
Object.entries(draft.values).forEach(([key, value]) => {
setValue(key, value);
});
} else {
// Пользователь отказался, очищаем черновик
await clearDraft();
}
};

restoreDraft();
}, []);

💡 Лучшие практики

✅ ДЕЛАЙТЕ

  • Используйте autoRestore: false для контролируемых форм
  • Вызывайте restoreValues(setValue, getValues) при монтировании
  • Отслеживайте изменения и вызывайте saveValues()
  • Очищайте черновик после успешной отправки
  • Используйте batchSaveInterval для лучшей производительности

❌ НЕ ДЕЛАЙТЕ

  • Не используйте autoRestore: true с React Hook Form
  • Не забывайте очищать черновик после отправки
  • Не сохраняйте при каждом нажатии клавиши без debounce
  • Не сохраняйте поля паролей (уже исключены по умолчанию)

📊 Оптимизация производительности

Debouncing

const { formRef } = useFormAutosave('my-form', {
debounceMs: 500, // Ждать 500мс после последнего изменения
});

Пакетирование

const { formRef } = useFormAutosave('my-form', {
batchSaveInterval: 5000, // Сохранять накопленные изменения каждые 5с
debounceMs: 300,
});

Выборочные поля

const { formRef } = useFormAutosave('my-form', {
whitelist: ['input[name="title"]', 'textarea[name="content"]'], // Сохранять только эти
});

🔗 Связанные ресурсы