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

Интеграция с Formik

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

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

Установка

npm install @form-guardian/react formik

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

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

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

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

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

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, 'Должно быть не менее 2 символов')
.required('Обязательное поле'),
email: Yup.string()
.email('Неверный email адрес')
.required('Обязательное поле'),
message: Yup.string()
.max(500, 'Должно быть не более 500 символов')
.required('Обязательное поле'),
});

function ContactForm() {
const formik = useFormik({
initialValues: {
name: '',
email: '',
message: '',
},
validationSchema,
onSubmit: async (values) => {
await clearDraft();
// Отправка в 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('Черновик сохранен');
},
});

// Восстановление при монтировании
useEffect(() => {
restoreValues(formik.setFieldValue, formik.values);
}, []);

// Автосохранение при изменениях
useEffect(() => {
if (formik.dirty) {
saveValues();
}
}, [formik.values]);

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

<form ref={formRef} onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="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">Сообщение *</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 ? 'Отправка...' : 'Отправить'}
</button>
</form>
</div>
);
}

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

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

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

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

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

Сохранять черновики только при прохождении валидации:

useEffect(() => {
const saveDraft = async () => {
// Тихая валидация
const errors = await formik.validateForm();

// Сохранять только если нет ошибок или пользователь взаимодействовал
if (Object.keys(errors).length === 0 || formik.dirty) {
saveValues();
}
};

if (formik.dirty) {
saveDraft();
}
}, [formik.values]);

Кастомное восстановление с подтверждением

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

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

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

if (shouldRestore) {
// Восстановление в Formik
Object.entries(draft.values).forEach(([key, value]) => {
formik.setFieldValue(key, value);
});
} else {
await clearDraft();
}
};

handleRestore();
}, []);

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

✅ ДЕЛАЙТЕ

  • Используйте autoRestore: false для контролируемых форм
  • Вызывайте restoreValues(formik.setFieldValue, formik.values) при монтировании
  • Сохраняйте при изменении formik.values когда formik.dirty равно true
  • Очищайте черновик после успешной отправки
  • Используйте debouncing для лучшей производительности

❌ НЕ ДЕЛАЙТЕ

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

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

Debouncing

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

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

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

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