React и обработка форм через Formik

React и обработка форм через Formik

  • 12 октября
  • читать 20 мин
Владимир Шайтан
Владимир Шайтан Senior Full Stack Developer в UKEESS Software House, Преподаватель Компьютерной школы Hillel.
Кирилл Громыко
Кирилл Громыко Frontend Developer

Многие веб-разработчики сталкивались со сложностями при создании и валидации форм. Сколько проблем возникало при написании собственных решений! Но сейчас существует множество инструментов, значительно улучшающих этот процесс. Сегодня мы рассмотрим несколько из них. Но сначала подробно разберём саму проблему, для того чтобы найти именно то решение, которое нам подойдёт лучше всего.

ПРОБЛЕМА

Я разобью проблемы на блоки для более логического описания, поскольку их набегает не мало и они не очень просты. Итак, разбираем проблемы нативного JavaScript:

1. Обработка состояния формы — если вы читаете статью о React, то вы уже должны были тем или иным образом сталкиваться с понятием state и component. В нашем случае (чистый JS) мы получим проблему именно в работе с состоянием формы, потому что для управления состоянием формы потребуется написать достаточно много кода. Он будет постоянно отслеживать изменения и обновлять данные в интерфейсе. Я уже молчу о том, что поля в форме могут быть связаны между собой.

2. Валидация — здесь аналогично, надо написать достаточно много кода для валидации, плюс к тому нам потребуется писать валидационные правила, которые все так любят.

3. Обработка событий — кроме того, что работа с моделью событий постоянно вводит начинающих в ступор, работая с чистым JS, нам нужно думать ещё и о количестве обработчиков, которые мы вешаем на элементы. От этого довольно сильно зависит performance страницы, не говоря о том, что некоторые события ведут себя не совсем очевидно, а кое-где и неправильно.

4. Обработка ошибок —здесь всё просто, в JS нет нативного способа для отображения сообщений или подсказок пользователям, и всю эту функциональность нам нужно будет писать ручками, а также тестировать.

5. Кроссбраузерность — помните, я выше писал, что некоторые события ведут себя не так, как должны. Так вот, в каких-то браузерах они ведут себя правильно. Осталось только нормализовать работу во всех браузерах, на которых мы тестируем.

6. Переиспользование кода — было бы очень хорошо перепользовать код, написанный для одной формы на другой. И так можно делать, но не используя вспомогательные инструменты, вы скорее дойдёте до дублирования, или напишете свою библиотеку;)

7. Стилизация —это наше всё, не забываем что кроме логики которую мы пишем, нужно дать пользователю понять хоть что-то. Например, что он удачно заполнил форму или что мы его не зарегистрировали, потому что ему ещё нет 18 лет.

В этом всём есть свои плюсы также, используя чистый JS, можно написать что-то очень гибкое и то, что будет хорошо работать в контексте именно вашего проекта. Но это требует времени, знаний и уровня навыков.

Следовательно, все аспекты, описанные выше, делают работу с формами довольно тяжёлой и объёмной с точки зрения написания кода. Именно поэтому мы, разработчики, так любим использовать вспомогательные инструменты, библиотеки и фреймворки.

Ну и как бонус, вот типичный кусок кода для обработки формы на чистом JS:

<!-- HTML: Структура формы -->

<form id="contactForm">
   <div>
       <label for="name">Ім'я:</label>
       <input type="text" id="name" name="name" />
       <span class="error" id="nameError"></span>
   </div>
   <div>
       <label for="email">Електронна пошта:</label>
       <input type="email" id="email" name="email" />
       <span class="error" id="emailError"></span>
   </div>
   <div>
       <label for="message">Повідомлення:</label>
       <textarea id="message" name="message"></textarea>
       <span class="error" id="messageError"></span>
   </div>
   <button type="submit">Відправити</button>
</form>

// JavaScript: Обробка та валідація форми
document.getElementById('contactForm').addEventListener('submit', function (event) {
   event.preventDefault(); // Відміняємо стандартну поведінку форми

   // Отримуємо значення полів
   const name = document.getElementById('name').value.trim();
   const email = document.getElementById('email').value.trim();
   const message = document.getElementById('message').value.trim();

   // Скидаємо повідомлення про помилки
   document.getElementById('nameError').textContent = '';
   document.getElementById('emailError').textContent = '';
   document.getElementById('messageError').textContent = '';

   let isValid = true;

   // Валідація імені
   if (name === '') {
       document.getElementById('nameError').textContent = 'Введіть ваше ім’я';
       isValid = false;
   }

   // Валідація електронної пошти
   const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
   if (email === '') {
       document.getElementById('emailError').textContent = 'Введіть вашу електронну пошту';
       isValid = false;
   } else if (!emailPattern.test(email)) {
       document.getElementById('emailError').textContent = 'Неправильний формат електронної пошти';
       isValid = false;
   }

   // Валідація повідомлення
   if (message === '') {
       document.getElementById('messageError').textContent = 'Введіть ваше повідомлення';
       isValid = false;
   }

   // Якщо форма пройшла валідацію
   if (isValid) {
       console.log('Форма успішно відправлена');
       console.log({ name, email, message });

       // Можна очистити поля форми або відправити дані на сервер
       document.getElementById('contactForm').reset();
   }
});

ПРЫГАЕМ В REACT

React упрощает работу с формами тем, что позволяет легко управлять состоянием через useState, что обеспечивает синхронизацию формы с интерфейсом. Он также упрощает обработку событий, минимизирует проблемы с кроссбраузерностью и позволяет повторно использовать код компонентов. Это делает работу с формами более эффективной и понятной.

import React, { useState } from 'react';

const SimpleForm = () => {
   const [formData, setFormData] = useState({ name: '', email: '' });

   const handleChange = (e) => {
       const { name, value } = e.target;
       setFormData({ ...formData, [name]: value });
   };

   const handleSubmit = (e) => {
       e.preventDefault();
       console.log(formData);
   };

   return (
       <form onSubmit={handleSubmit}>
           <input
               type="text"
               name="name"
               value={formData.name}
               onChange={handleChange}
               placeholder="Ім'я"
           />
           <input
               type="email"
               name="email"
               value={formData.email}
               onChange={handleChange}
               placeholder="Електронна пошта"
           />
           <button type="submit">Відправити</button>
       </form>
   );
};

export default SimpleForm;

Зная базу работы с React, в этом коде разобраться достаточно просто, здесь главная логика состоит в том, что наши input элементы являются управляемыми компонентами и рендерят только то, что есть в стейте formData, а синхронизируются со стейтом через handleSubmit обработчик.

В этом случае работа с готовым инструментарием уже намного упрощает работу с формами, чем в нативном JS. Но можно ещё больше упростить этот процесс, используя разработанные под эти задачи библиотеки, например Formik.

FORMIC — БИБЛИОТЕКА ДЛЯ РАБОТЫ С ФОРМАМИ В REACT

Formik — это небольшая группа компонентов и хуков для создания форм в React и React Native. Он значительно облегчает тестирование, рефакторинг и понимание ваших форм.

Основные функции Formik:

  • получение значений из состояния формы и их запись в состояние формы;
  • валидация и сообщения об ошибках;
  • обработка отправки формы;
  • уменьшение объёма кода благодаря автоматизации многих задач, что снижает вероятность ошибок.

Приведу базовый пример обработки формы через Formik.

import React from 'react';
import { useFormik } from 'formik';

const SimpleFormikForm = () => {
   // Використовуємо useFormik для обробки логіки форми
   const formik = useFormik({
       initialValues: {
           name: '',
           email: '',
       },
       onSubmit: (values) => {
           alert(`Ім'я: ${values.name}, Електронна пошта: ${values.email}`);
       },
   });

   return (
       <form onSubmit={formik.handleSubmit}>
           <div>
               <label htmlFor="name">Ім'я:</label>
               <input
                   id="name"
                   name="name"
                   type="text"
                   onChange={formik.handleChange}
                   value={formik.values.name}
               />
           </div>

           <div>
               <label htmlFor="email">Електронна пошта:</label>
               <input
                   id="email"
                   name="email"
                   type="email"
                   onChange={formik.handleChange}
                   value={formik.values.email}
               />
           </div>

           <button type="submit">Відправити</button>
       </form>
   );
};

export default SimpleFormikForm;

Пошагово:

1. Импорт и инициализация Formik

import React from 'react';
import { useFormik } from 'formik';

2. Настройка useFormik

const formik = useFormik({
   initialValues: {
       name: '',
       email: '',
   },
   onSubmit: (values) => {
       alert(`Ім'я: ${values.name}, Електронна пошта: ${values.email}`);
   },
});

initialValues: это первоначальное значение для нашей формы. В этом случае мы определили два поля: name и email, и оба имеют пустые значения.

onSubmit: это функция, которая вызывается при отправке формы. В нашем случае мы просто выводим сообщения с данными, которые ввел пользователь.

3. Рендеринг формы

return (
   <form onSubmit={formik.handleSubmit}>
       <div>
           <label htmlFor="name">Ім'я:</label>
           <input
               id="name"
               name="name"
               type="text"
               onChange={formik.handleChange}
               value={formik.values.name}
           />
       </div>

Мы используем обычный HTML-тег <form>, но подключаем его к Formik через onSubmit={formik.handleSubmit}. Это гарантирует, что Formik обработает отправку формы.

handleChange от Formik привязывается к onChange каждого инпута, чтобы автоматически обновлять значения в состоянии. value={formik.values.name} указывает значение инпута на текущее состояние формы.

4. Обработка второго поля

<div>
   <label htmlFor="email">Електронна пошта:</label>
   <input
       id="email"
       name="email"
       type="email"
       onChange={formik.handleChange}
       value={formik.values.email}
   />
</div>

Аналогично, поле email подключено к Formik, используя formik.handleChange и formik.values.email.

5. Кнопка отправки

<button type="submit">Відправити</button>
</form>
);

Кнопка submit вызывает formik.handleSubmit, когда пользователь нажимает на нее, отправляя значение формы для обработки.

YUP — БИБЛИОТЕКА ДЛЯ ВАЛИДАЦИИ ДАННЫХ

Yup — это библиотека для валидации данных и создания схем объектов. Она позволяет легко определять правила валидации для объектов и полей, используя удобный и читаемый синтаксис. Yup работает отлично как самостоятельно, так и в сочетании с другими библиотеками.

Пример использования Yup на чистом JavaScript.

// Імпортуємо бібліотеку Yup
import * as Yup from 'yup';

// Створюємо схему валідації
const validationSchema = Yup.object().shape({
   name: Yup.string()
       .required('Ім’я є обов’язковим')
       .min(2, 'Ім’я має бути не менше 2 символів'),
   email: Yup.string()
       .required('Електронна пошта є обов’язковою')
       .email('Невірний формат електронної пошти'),
   age: Yup.number()
       .required('Вік є обов’язковим')
       .min(18, 'Вам має бути 18 років або більше')
});

// Дані, які потрібно перевірити
const formData = {
   name: 'Іван',
   email: 'ivan@example.com',
   age: 17,
};

// Валідація даних
validationSchema
   .validate(formData, { abortEarly: false }) // abortEarly: false - показує всі помилки
   .then((validData) => {
       console.log('Дані валідні:', validData);
   })
   .catch((errors) => {
       console.log('Помилки валідації:', errors.errors);
   });

Что происходит в примере?

  1. Мы создали схему validationSchema с правилами валидации для полей name, email, и age.
  2. Далее мы проверяем объект formData с помощью validate().
  3. Если данные валидны, они будут отображены в консоли. Если нет, получим массив ошибок.

Это позволяет легко определять и проверять правила валидации, делая код чистым и удобным.

YUP + FORMIC = ВЕЛИКОЛЕПНАЯ ПАРА

Formik + Yup — это действительно отличная пара, потому что они просто работают вместе. Formik делает работу с формами в React удобной, но валидация — это другая история, и здесь появляется Yup.

Итак, почему они так хорошо подходят друг другу?

Formik уже знает о существовании Yup, потому не нужно заниматься излишней интеграцией. Вы просто указываете validationSchema в Formik, переда'те туда схему Yup, и все он сам позаботится о валидации.

Yup — это такая штука, позволяющая описать валидацию просто и декларативно. Никаких ручных проверок типа «если пустое — покажи ошибку». Вы сразу описываете все правила и они работают.

Formik самостоятельно отслеживает состояние формы, ошибки, касания полей — и с Yup это всё работает автоматически. Вам не нужно возиться с тем, когда и как показывать сообщения об ошибках, Formik это берёт на себя.

Если вам нужно добавить свои правила валидации, Yup это позволяет сделать. То есть эта парочка позволяет легко и быстро настроить валидацию, и в то же время они настолько гибки, что подходят для любых случаев.

Поэтому Yup+Formik — это реально удобно, быстро, и работает отлично почти с коробки.

Пример ниже будет труднее предыдущих, но он демонстрирует нетривиальную работу самой Formik, а также реализует валидацию данных через Yup, что облегчает комплексную логику компонента динамической формы.

Главное — не торопись и вдумчиво читай код, тогда всё точно станет понятно.

import React from 'react';
import { useFormik, FieldArray } from 'formik';
import * as Yup from 'yup'; // Імпортуємо Yup для валідації

const DynamicFormikForm = () => {
   // Створюємо схему валідації за допомогою Yup
   const validationSchema = Yup.object().shape({
       friends: Yup.array()
           .of(
               Yup.object().shape({
                   name: Yup.string()
                       .required("Введіть ім'я"), // Перевірка на обов'язкове заповнення
                   email: Yup.string()
                       .email('Неправильний формат електронної пошти') // Перевірка формату
                       .required('Введіть електронну пошту'), // Перевірка на обов'язкове заповнення
               })
           )
           .min(1, 'Додайте хоча б одного друга'), // Перевірка, щоб був хоча б один друг
   });

   // Використовуємо useFormik для роботи з формою
   const formik = useFormik({
       initialValues: {
           friends: [{ name: '', email: '' }], // Початкове значення масиву з одним об'єктом
       },
       validationSchema, // Підключаємо нашу схему валідації Yup
       onSubmit: (values) => {
           console.log('Відправлені значення:', values); // Виводимо у консоль значення при відправці
           alert('Форма успішно відправлена!'); // Повідомлення при успішній відправці
       },
   });

   return (
       <form onSubmit={formik.handleSubmit}>
           <h2>Друзі</h2>
           <FieldArray
               name="friends"
               render={(arrayHelpers) => (
                   <div>
                       {formik.values.friends && formik.values.friends.length > 0 ? (
                           formik.values.friends.map((friend, index) => (
                               <div key={index} style={{ marginBottom: '15px' }}>
                                   <div>
                                       <label htmlFor={`friends.${index}.name`}>Ім'я:</label>
                                       <input
                                           type="text"
                                           name={`friends.${index}.name`}
                                           value={friend.name}
                                           onChange={formik.handleChange}
                                           onBlur={formik.handleBlur}
                                       />
                                       {formik.touched.friends &&
                                           formik.touched.friends[index] &&
                                           formik.errors.friends &&
                                           formik.errors.friends[index] &&
                                           formik.errors.friends[index].name && (
                                               <div className="error">
                                                   {formik.errors.friends[index].name}
                                               </div>
                                           )}
                                   </div>
                                   <div>
                                       <label htmlFor={`friends.${index}.email`}>Електронна пошта:</label>
                                       <input
                                           type="email"
                                           name={`friends.${index}.email`}
                                           value={friend.email}
                                           onChange={formik.handleChange}
                                           onBlur={formik.handleBlur}
                                       />
                                       {formik.touched.friends &&
                                           formik.touched.friends[index] &&
                                           formik.errors.friends &&
                                           formik.errors.friends[index] &&
                                           formik.errors.friends[index].email && (
                                               <div className="error">
                                                   {formik.errors.friends[index].email}
                                               </div>
                                           )}
                                   </div>
                                   <button
                                       type="button"
                                       onClick={() => arrayHelpers.remove(index)} // Видалення друга
                                   >
                                       Видалити
                                   </button>
                               </div>
                           ))
                       ) : (
                           <div>Додайте хоча б одного друга</div>
                       )}
                       <button
                           type="button"
                           onClick={() => arrayHelpers.push({ name: '', email: '' })} // Додавання нового друга
                       >
                           Додати друга
                       </button>
                   </div>
               )}
           />

           <button type="submit">Відправити</button>
       </form>
   );
};

export default DynamicFormikForm;

Объясняю:

1. Схема валидации Yup

const validationSchema = Yup.object().shape({
   friends: Yup.array()
       .of(
           Yup.object().shape({
               name: Yup.string()
                   .required("Введіть ім'я"), // Перевірка на обов'язкове заповнення
               email: Yup.string()
                   .email('Неправильний формат електронної пошти') // Перевірка формату
                   .required('Введіть електронну пошту'), // Перевірка на обов'язкове заповнення
           })
       )
       .min(1, 'Додайте хоча б одного друга'), // Перевірка, щоб був хоча б один друг
});

Это основная магия Yup — вот, где задаются все правила. Yup.object().shape({...}) позволяет определить структуру объекта. Наш friends — это массив (Yup.array()), каждый элемент которого должен быть объектом с полями name и email. Короче, Yup ясно описывает всё, что мы ожидаем увидеть в форме, и вы сразу видите все правила в одном месте.

2. Использование useFormik из Yup

const formik = useFormik({
   initialValues: {
       friends: [{ name: '', email: '' }],
   },
   validationSchema, // Підключаємо нашу схему валідації Yup
   onSubmit: (values) => {
       console.log('Відправлені значення:', values);
       alert('Форма успішно відправлена!');
   },
});

Это уже знакомый useFormik, но обрати внимание, как мы просто подставили validationSchema. Formik теперь знает, что ему делать с данными, и больше не надо возиться с функцией validate — Yup всё сделает за нас. Вот здесь и видна настоящая сила их «пары». Минимум кода — максимум результата.

3. Отражение ошибок

{formik.touched.friends &&
formik.touched.friends[index] &&
formik.errors.friends &&
formik.errors.friends[index] &&
formik.errors.friends[index].name && (
   <div className="error">
       {formik.errors.friends[index].name}
   </div>
)}

Здесь немного запутано, но важно разобраться. formik.touched указывает, что пользователь уже взаимодействовал с этим полем, а formik.errors содержит сообщение об ошибках от Yup. Если все эти условия соблюдаются, мы показываем ошибку. Да, это кажется громоздким, но такая проверка гарантирует, что мы не показываем ошибку сразу, пока пользователь ещё не работал с полем.

4. FieldArray и динамические поля

<FieldArray
   name="friends"
   render={(arrayHelpers) => (
       <div>
           {formik.values.friends && formik.values.friends.length > 0 ? (
               formik.values.friends.map((friend, index) => (
                   <div key={index} style={{ marginBottom: '15px' }}>
                       {/* Тут ваші поля для name і email */}
                       <button
                           type="button"
                           onClick={() => arrayHelpers.remove(index)} // Видалення друга
                       >
                           Видалити
                       </button>
                   </div>
               ))
           ) : (
               <div>Додайте хоча б одного друга</div>
           )}
           <button
               type="button"
               onClick={() => arrayHelpers.push({ name: '', email: '' })} // Додавання нового друга
           >
               Додати друга
           </button>
       </div>
   )}
/>

Это главный прикол с динамическими полями. FieldArray позволяет работать с массивом данных в форме. Через arrayHelpers мы можем добавлять (push), или удалять (remove) друзей. И когда Yup говорит, что должен быть хотя бы один друг, Formik и FieldArray помогают нам сохранять это состояние. По сути, это означает, что любое изменение в массиве «друзей» мгновенно отображается в форме.

ВЫВОД

Итак, подводя итоги. Работа с формами в чистом JavaScript — это та ещё головная боль: множество кода, сложная валидация, ручная обработка ошибок и куча повторений. React значительно упрощает этот процесс, позволяя держать состояние формы под контролем и обрабатывать действия, но всё равно много рутины остаётся на нас.

И здесь на сцену выходит Formik — он помогает сделать все ещё проще, взяв на себя обработку состояния и отправку данных формы. В сочетании с Yup он превращает валидацию во что-то максимально декларативное и удобное. Вся логика валидации сразу видна, её легко поддерживать и, главное, всё работает автоматически вместе с Formik.

Таким образом, пара Formik + Yup действительно облегчает жизнь разработчика, позволяя сосредоточиться на бизнес-логике, а не на том, как обработать каждую мелкую ошибку или валидацию формы. В результате вы получаете мощный и гибкий инструмент, экономящий время и нервы. Так что если вы еще не попробовали — самое время дать этим библиотекам шанс.

Рекомендуем публикации по теме