TanStack Form проти Formik: що переможе у світі React-форм у 2025

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Привіт, народ! За штурвалом — ваша Юлія Яворська, фронтендер-джедай зі Skillzzy! Провела чимало годин у вирі коду, намагаючись змусити інтерфейси користувача бути не лише функціональними, але й спритними. Свою кар’єру в IT почала доволі недавно, два роки тому, але зацікавленість розробкою була ще з 2018 року. Я — свого роду «світчер», оскільки окрім розробки вже близько 8 років займаюся онлайн-маркетингом (Google Ads), проєктним менеджментом та керую командами.

Сьогодні я хочу запросити вас у невеличку, сподіваюся, пізнавальну подорож у світ React-форм. Ми зазирнемо під капот двох популярних гравців на цій арені: Formik, що вже встиг стати своєрідним стандартом, та його більш молодого, але амбітного конкурента — TanStack Form. Можливо, ви вже знайомі з Formik, використовували його у своїх проєктах і цінуєте за простоту. Але чи замислювалися ви про те, що за цією зручністю можуть ховатися неочевидні пастки продуктивності?

Тож наповнюйте кубки кавою (або чим ви там підтримуєте свій кодерський дух 😉) та погнали! Розберемося разом, хто ж переможе у цій битві титанів форм у 2025 році!

Formik: «класика жанру» чи «тормоз» прогресу

Коли мова заходить про React-форми, Formik нерідко спадає на думку першим. Проте за всією його простотою ховається потенційна проблема, яка може стати критичною з часом — надмірні перерендери, що виникають при кожній зміні поля.

Як наслідок відбувається затримка інтерфейсу, що вбиває user experience and user satisfaction. А як ми всі знаємо, наші користувачі не полюбляють чекати, отож потрібно вирішувати це питання та «take an even deeper dive into», як кажуть класики.

Налаштування та конфігурація

Отож почнемо з налаштування обох бібліотек, яке є доволі легке та інтуїтивне:

  • Formik
    • npm install formik —save or yarn add formik
  • Tanstack | React-приклад
    • npm i @tanstack/react-form
    • yarn add @tanstack/react-form

Пропоную заглянути додатково до офіційної документації на випадок зміни налаштувань.

Розпаковка «боксів»: Formik vs TanStack — хто швидше «заведеться»

Продовжуючи тему продуктивності варто зазначити, що розмір залежностей має безпосередній вплив на швидкість роботи та завантаження вебзастосунків. Маю на увазі, що розмір бандла є важливим фактором.

TanStack Form очевидно демонструє значно менший розмір бандла порівняно з Formik, однак точні цифри можуть варіюватися залежно від версії та конфігурації. Це означає менший обсяг JavaScript, який необхідно завантажити браузеру, що позитивно впливає на час першого відображення сторінки та загальну продуктивність застосунку.

На проєкті Skillzzy вибір легкої та ефективної бібліотеки став для нас пріоритетом.

Під час розробки модальних вікон, що містили форми, наша команда зіткнулася з проблемою частих та надмірних перерендерів компонентів навіть при незначних змінах у полях. Це суттєво впливає на продуктивність та перформанс платформи. Після ретельного аналізу, або як кажуть програмісти, «research», було виявлено особливість внутрішньої реалізації Formik.

Formik за замовчуванням схильний порівнювати об’єкт значень форми при кожній зміні поля, навіть якщо зміни були мінімальними. Цей процес глибокого порівняння (deep comparison*) може бути обчислювально витратним, особливо для великих і складних форм, що і призводило до непотрібних перерендерів компонентів, які фактично не залежали від змінених даних.

*Хоча прямого твердження на кшталт «Formik завжди глибоко порівнює» немає, однак те, як Formik відстежує зміни та ініціює оновлення, натякає на це.

Звичайно, є способи вирішення цієї проблеми, такі як хук React.memo. Однак такий підхід часто призводить до нагромадження значної кількості додаткового коду та ускладнює підтримку форми. Крім того, він вимагає від розробника постійного контролю за тим, які саме частини форми потрібно оновлювати.

Міграція з Formik на TanStack: hardcore чи «легка прогулянка»

На противагу TanStack Form пропонує фундаментально інший підхід. Основні переваги TanStack Form:

  • Використання неконтрольованих компонентів (за замовчуванням) з прямим зв’язуванням DOM → менше повторних рендерів. DOM сам зберігає вхідне значення (як у звичайному HTML). React зчитує значення тільки тоді, коли це необхідно (наприклад, onSubmit).
  • Очевидно менший розмір банду. ~9kB (vs Formik’s ~13kB).
  • Гнучка валідація. Бездоганно працює з валідацією Zod, Yup або користувацькою валідацією. Підтримує перевірку як на рівні полів, так і на рівні всієї форми (з використанням схем).
  • Відсутність залежностей. Framework-agnostic

TanStack — універсальний солдат?

Вище згадувала про agnostic-підхід і пропоную в цій частині розглянути, що це таке. Якщо коротко, то дослівно «незалежний від будь-яких фреймворків». Така незалежність досягається завдяки тому, що TanStack Form не використовує специфічні для певного фреймворку API або концепції у своєму основному коді.

Але виникає логічне питання, як тоді він інтегрується з конкретними фреймворками? Якщо думати логічно, з’являється ідея, що «мають же бути якісь механізми, які адаптують системи одна до іншої».

І тут в гру входить такий патерн програмування як адаптери. Не чули?! Не страшно, я теж відкрила цей патерн за підказкою мого викладача та ментора Володимир Шайтан, людини з майже десятьма роками досвіду в розробці та технологіях.

Адаптер, ти хто такий?

Візьмемо пояснення з refactoring.guru. Адаптер — це структурний патерн проєктування, що дає змогу об’єктам із несумісними інтерфейсами працювати разом. Це об’єкт-перекладач, який трансформує інтерфейс або дані одного об’єкта таким чином, щоб він став зрозумілим іншому об’єкту. Адаптер загортає один з об’єктів так, що інший об’єкт навіть не підозрює про існування першого.

До прикладу, для React адаптер надає React Hooks, такі як useForm або useField тощо. Ці хуки інкапсулюють логіку TanStack Form і роблять її доступною у функціональних компонентах React, використовуючи реактивний стан та механізми рендерингу React. Приклад використання можна побачити в самому коді нижче.

Аналогічно для Vue існує окремий адаптер, який надає композиційні функції (Composition API) або опції (Options API) для інтеграції з реактивною системою Vue. Серед них useForm, useField, тощо.

Реальний кейс: «перевзуваємо» Formik на TanStack на льоту

А тепер пропоную розглянути процес переходу з Formik на Tanstack Form на прикладі нашого проєкту.

Tan Stack Form — FeedbackProjectModal.jsx

import { Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { useForm } from '@tanstack/react-form';
import { useStore } from '@tanstack/react-form';
import { useSelector } from 'react-redux';
import { useSnackbar } from 'notistack';
import { useCreateFeedbackMutation } from '@redux/api/slices/feedbackProjectModalApiSlice.js';
import ButtonDef from '@components/FormsComponents/Buttons/ButtonDef';
import FormSelect from '@components/FormsComponents/Inputs/FormSelect';
import TextAreaInput from '@components/FormsComponents/Inputs/TextAreaInput';
import { FeedbackProjectModalSchema } from '@utils/validationSchemas/index';
import { modalNames } from '@utils/constants/modalNames.js';
import { useModalController } from '@utils/hooks/useModalController.js';
import { feedbackOptions } from './constants';
import { styles } from './FeedbackProjectModal.styles';
const FeedbackProjectModal = () => {
 const { t } = useTranslation();
 const { enqueueSnackbar } = useSnackbar();
 const { id } = useSelector((state) => state.auth.user.data);
 const { closeModal } = useModalController();
 const [createFeedback, { isLoading }] = useCreateFeedbackMutation();
 const from = useForm({
   defaultValues: {
     select: '',
     feedbackText: '',
   },
   validators: { onChange: FeedbackProjectModalSchema },
   onSubmit: async ({ value }) => {
     try {
       await createFeedback({
         userId: id,
         type: value.select,
         text: value.feedbackText,
       });
       enqueueSnackbar(t('modal.feedbackProjectModal.success'), { variant: 'success' });
     } catch (error) {
       enqueueSnackbar(t('modal.feedbackProjectModal.error_429'), { variant: 'error' });
     }
     closeModal(modalNames.feedbackProjectModal);
   },
 });
 const isDirty = useStore(form.store, (state) => state.isDirty);
 const isValid = useStore(form.store, (state) => state.isValid);
 return (
   <>
     <Typography sx={styles.title} variant='h6'>
       {t('modal.feedbackProjectModal.title')}
     </Typography>
     <form
       onSubmit={(e) => {
         e.preventDefault();
         form.handleSubmit();
       }}
     >
       <form.Field name='select'>
         {(field) => (
           <FormSelect
             isTranslated
             required
             countries={feedbackOptions}
             error={field.state?.meta?.errors?.length > 0}
             handleBlur={field.handleBlur}
             handleChange={(e) => {
               field.handleChange(e.target.value);
             }}
             helperText={t(field.state?.meta?.errors[0]?.message)}
             label={t('modal.feedbackProjectModal.formSelectLabel')}
             name={field.name}
             value={field.state.value}
             variant='outlined'
           />
         )}
       </form.Field>
       <form.Field name='feedbackText'>
         {(field) => (
           <TextAreaInput
             required
             error={field.state?.meta?.errors?.length > 0}
             handleBlur={field.handleBlur}
             handleChange={(e) => {
               field.handleChange(e.target.value);
             }}
             helperText={t(field.state?.meta?.errors[0]?.message)}
             label={t('modal.feedbackProjectModal.textAreaLabel')}
             name={field.name}
             placeholder={t('modal.feedbackProjectModal.textPlaceholder')}
             value={field.state.value}
           />
         )}
       </form.Field>
       <ButtonDef
         fullWidth
         disabled={!isDirty || !isValid}
         label={t('modal.feedbackProjectModal.button')}
         loading={isLoading}
         sx={styles.btn}
         type='submit'
         variant='contained'
       />
     </form>
   </>
 );
};
export default FeedbackProjectModal;

На що варто звернути увагу:

1. Імпорт useForm та useStore з @tanstack/react-form. Замість імпорту useFormik з Formik імпортуємо хук useForm та хук для реактивності userStore з бібліотеки Tanstack Form.

import { useForm } from '@tanstack/react-form';
import { useStore } from '@tanstack/react-form';

2. Ініціалізація форми за допомогою useForm. Замість використання хука useFormik ініціалізуємо форму за допомогою хука useForm від Tanstack Form. Цей хук приймає об’єкт конфігурації, схожий на useFormik, але з деякими відмінностями в структурі та можливостях.

const from = useForm({
   defaultValues: {
     select: '',
     feedbackText: '',
   },
   validators: { onChange: FeedbackProjectModalSchema },
   onSubmit: async ({ value }) => {
     // ... код обробки відправки форми
   },
 });
  • default Values. Схожий принцип, визначає початкові значення полів форми.
  • validators. Визначає схему валідації. У Tanstack Form валідація може бути визначена як на подію onChange, так і на onSubmit.
  • onSubmit. Функція, яка викликається при успішній відправці форми. Зверніть увагу, що значення форми передаються в об’єкті value.

3. Використання form.Field для реєстрації полів. Замість компонента Field або ручного підключення onChange, onBlur та value до кожного поля, Tanstack Form використовує компонент form.Field. Цей компонент приймає пропс name (назву поля) і рендер-пропс (render prop) або children у вигляді функції. Ця функція отримує об’єкт field, який містить необхідні обробники та стан поля.

<form.Field name='select'>
         {(field) => (
           <FormSelect
             isTranslated
             required
             countries={feedbackOptions}
             error={field.state?.meta?.errors?.length > 0}
             handleBlur={field.handleBlur}
             handleChange={(e) => {
               field.handleChange(e.target.value);
             }}
             helperText={t(field.state?.meta?.errors[0]?.message)}
             label={t('modal.feedbackProjectModal.formSelectLabel')}
             name={field.name}
             value={field.state.value}
             variant='outlined'
           />
         )}
       </form.Field>
       <form.Field name='feedbackText'>
         {(field) => (
           <TextAreaInput
             required
             error={field.state?.meta?.errors?.length > 0}
             handleBlur={field.handleBlur}
             handleChange={(e) => {
               field.handleChange(e.target.value);
             }}
             helperText={t(field.state?.meta?.errors[0]?.message)}
             label={t('modal.feedbackProjectModal.textAreaLabel')}
             name={field.name}
             placeholder={t('modal.feedbackProjectModal.textPlaceholder')}
             value={field.state.value}
           />
         )}
       </form.Field>

4. Обробка відправки форми за допомогою form.handleSubmit. Замість виклику formik.handleSubmit, використовуємо метод form.handleSubmit об’єкта form, отриманого від useForm.

<form
       onSubmit={(e) => {
         e.preventDefault();
         form.handleSubmit();
       }}
     >
 {/* ... */}
</form>

5. Реактивність за допомогою useStore. Tanstack Form надає гнучкі механізми для підписки на зміни стану форми та окремих полів, що дозволяє оптимізувати рендеринг. У цьому прикладі використовується хук useStore для отримання реактивних значень isDirty (чи була змінена хоча б одне поле) та isValid (чи валідна форма).

 const isDirty = useStore(form.store, (state) => state.isDirty);
 const isValid = useStore(form.store, (state) => state.isValid);
  <ButtonDef
        ...
         disabled={!isDirty || !isValid}
        ...
       />
     </form>
   </>
  • useStore(form.store, selector) приймає сховище форми (form.store) та селектор-функцію. Селектор-функція отримує поточний стан сховища і повертає значення, на яке потрібно підписатися. Компонент буде перерендерено тільки тоді, коли значення, що повертається селектором, зміниться.

Formik — FeedbackProjectModal.jsx

import { Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { useFormik } from 'formik';
import { useSelector } from 'react-redux';
import { useSnackbar } from 'notistack';
import { useCreateFeedbackMutation } from '@redux/api/slices/feedbackProjectModalApiSlice.js';
import ButtonDef from '@components/FormsComponents/Buttons/ButtonDef';
import FormSelect from '@components/FormsComponents/Inputs/FormSelect';
import TextAreaInput from '@components/FormsComponents/Inputs/TextAreaInput';
import { FeedbackProjectModalSchema } from '@utils/validationSchemas/index';
import { modalNames } from '@utils/constants/modalNames.js';
import { useModalController } from '@utils/hooks/useModalController.js';
import { feedbackOptions } from './constants';
import { styles } from './FeedbackProjectModal.styles';
const initialValues = {
 select: '',
 feedbackText: '',
};
const FeedbackProjectModal = () => {
 const { t } = useTranslation();
 const { enqueueSnackbar } = useSnackbar();
 const { id } = useSelector((state) => state.auth.user.data);
 const { closeModal } = useModalController();
 const [createFeedback, { isLoading }] = useCreateFeedbackMutation();
 const onSubmit = async (values) => {
   try {
     await createFeedback({
       userId: id,
       type: values.select,
       text: values.feedbackText,
     });
     enqueueSnackbar(t('modal.feedbackProjectModal.success'), { variant: 'success' });
   } catch (error) {
     enqueueSnackbar(t('modal.feedbackProjectModal.error_429'), { variant: 'error' });
   }
   closeModal(modalNames.feedbackProjectModal);
 };
 const formik = useFormik({
   initialValues,
   validationSchema: FeedbackProjectModalSchema,
   onSubmit,
 });
 return (
   <>
     <Typography sx={styles.title} variant='h6'>
       {t('modal.feedbackProjectModal.title')}
     </Typography>
     <form onSubmit={formik.handleSubmit}>
       <FormSelect
         isTranslated
         required
         countries={feedbackOptions}
         handleBlur={formik.handleBlur}
         handleChange={formik.handleChange}
         label={t('modal.feedbackProjectModal.formSelectLabel')}
         name='select'
         value={formik.values.select}
         variant='outlined'
       />
       <TextAreaInput
         required
         handleBlur={formik.handleBlur}
         handleChange={formik.handleChange}
         label={t('modal.feedbackProjectModal.textAreaLabel')}
         name='feedbackText'
         placeholder={t('modal.feedbackProjectModal.textPlaceholder')}
         value={formik.values.feedbackText}
       />
       <ButtonDef
         fullWidth
         disabled={!formik.isValid || !formik.dirty || isLoading}
         label={t('modal.feedbackProjectModal.button')}
         loading={isLoading}
         sx={styles.btn}
         type='submit'
         variant='contained'
       />
     </form>
   </>
 );
};
export default FeedbackProjectModal;

Фінальний раунд: хто ж забере корону у 2025

Хоча на момент написання статті бракує прямих, кількісних бенчмарків, які б порівнювали продуктивність TanStack Form та Formik у ідентичних сценаріях, існуючі дані та архітектурні відмінності (менший розмір бандла TanStack Form, використання неконтрольованих компонентів за замовчуванням, framework agnostic) дають валідні підстави для певних висновків.

А саме: TanStack Form має значний потенціал стати серйозним конкурентом Formik у 2025 році та навіть перевершити його в певних аспектах, що і підтвердив наш практичний досвід 🚀

Звісно, Formik залишається потужною та добре за рекомендованою бібліотекою з великою спільнотою, і, незважаючи на те, що новіші рішення, такі як TanStack Form, набирають популярності, Formik все ще є стандартом у багатьох великих компаніях. Через складність міграції Formik все ще зберігає свою поширеність, що робить навички роботи з ним важливими для розробників. Однак якщо ви стикаєтеся з проблемами продуктивності у ваших формах або шукаєте більш сучасний та гнучкий інструмент, TanStack Form безумовно заслуговує на увагу та може стати вдалим вибором для вашого наступного проєкту або для рефакторингу існуючих.

Сподіваємося, цей практичний приклад та огляд допоможуть прийняти обґрунтоване рішення щодо використання TanStack Form у вашій розробці. Не бійтеся експериментувати та використовувати інструменти, які найкраще відповідають вашим потребам!

З любов’ю (і парою задебажених годин), ваш Front-End Developer, який нарешті розібрався з формами 😉

Чекаю на Ваші коментарі та фідбеки.

👍ПодобаєтьсяСподобалось3
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

На мою думку переможе UI бібліотека, що використовується на проекті і у якій, скоріш за все, є свої компоненти форми тощо.
П.С. Загалом в проектах великих компаній так і є... бо нахіба брати ще одну бібліотеку для чогось, якщо в тебе вже це є. :)

Хотілось б ще почитати про порівняння з React Hook Form. Для мене саме ця бібліотека є «стандартом».
Formik — динозавр
TanStack Form — ще зовсім молодий)

Дякую Олег за відгук!
Власне Денис нижче теж звернув на це увагу. Думаю варто буде написати і другу статтю 😁
TanStack Form дійсно молодий, але доволі амбітний 🤘

Мені легко читалося! Оригінальний і доступний стиль подачі! 🤩

Дякую за приділену увагу! Хотілось дійсно написати легку статтю на скільки це можливо

Імпорти типу @redux/ діч звісно

Дякую за коментар!
Андрій, можете обгрунтувати свою думку, тому що мені важливий валідний фітбек для подальшого розвитку та підвищення знань

Вау дякую за статтю! Тепер я розумію чому Formik загадується не в дуже гарному світі. Дуже круто що у статті є порівняння з чимось а не просто пояснення проблеми без розуміння як з цим боротися. Обов’язково спробую тепер Tanstack на практиці!

Круто 🔥 Дякую за комент! Старалась привести практичний приклад, а не суху теорію

З одного боку прикольна стаття, з іншого...
Хтось взагалі використовує зараз Formik?
В нас вже давно відмовились, бо вони дуже погано підтримують свою бібліотеку. В них остання версія 2.4.6 була запаблішена рік назад. А 2.4.5 два роки назад. Перехід з версії 2.2.9 до 2.2.10 зайняв взагалі два роки. Дуже давно перейшли на React Hook Form і взагалі не жалкуємо. Formik бачив тільки в легасі проєктах.
Тому дуже дякую за статтю, але маю надію побачити щось таке про React Hook Form і TanStack Form))

Дуже дякую Денис! Як світчеру мені неоціненно приємно, що приділили час статті.
Абсолютно з Вами погоджусь, що Formik погано підтримується.
Можливо відповім як новачок, але для якихось pet project це непоганий solution, бо Formik доволі легкий в розумінні. Для комерціії однозначно тут перевага на боці React Hook Form або Tanstack, який набирає популярності.

А в чому це React Hook Form складніше за Formik?😳
І знову ж навіщо використовувати навіть в пет проєкті бібліотеку, яка погано підтримується?
Це ж залишає звичку до не найкращої технології, яка як невдалий вибір і для комерції, так і для пет проєкту, якщо ви переросте в щось серйозне

Вибачте, якщо здається що я прискіпуюсь до вас. Це не так)
Просто коли бачиш заголовок статті зі словами «що переможе у світі React-форм у 2025» ось Formik це останнє, про що ти думаєшь))
Це так само як порівнювати Redux і Zustand у 2025 році. Звісно сам Redux нікуди не дівся і треба знати його, хоча б основи навіть молодим розробникам. Бо ніколи не знаєш коли тобі дадуть проєкт з легасі (теж саме стосується і класових компонентів в React). Але порівнювати це все вже нема сенсу у світі де існує Valtio, jotai і TanStack Query. Бо ось тут вже дійсно цікаво що з них обрати, чому, або як комбінувати. І Redux у 2025 має ну дуже мало причин заводити його у проєкт, або не заміняти. Ось такий вам приклад для порівняння)

Не переставайте писати. Написано дуже гарно. Ви молодець)
Буду чекати нові статті від вас)

Навпаки, вдячна, що поділились свою точку зору та підтримали. Змогли трохи подискутувати, на скільки це можливо з світчером та початківцем:) Круто 🤝

Про Formik я згоден (ніколи ним не користувався в комерційному проекті), але Redux нікуди не дівся і поки не дінеться. Наприклад у проектах корпорацій, як і де-які інші мови програмування, підходи чи бібіліотеки.

Мала тут на увазі, що Formik легше для розуміння ніж Tanstack, з особистого досвіду склалось, що з Formik познайомилась раніше. А от з Tanstack треба трошки було посидіти та покопатись у документації. Але все приходить з практикою та досвідом. Сподіваюсь за пару років теж зможу вільно жонглювати бібліотеками:)

Дякую за зрозуміле пояснення в цій темі!!
Нещодавно займаюсь IT-програмуванням, тож для мене це важливо!!

Дякую за коментар!

Робота з формами — не найочевидніше місце для оптимізації, але саме там часто ховається performance bottleneck. Не кожен розробник глибоко копає в оптимізацію форм, бандлсайз, патерни адаптерів і порівняння ререндерів.

Дякую за матеріал

Абсолютно праві! Надихалась є Skillzzy проєктом, в якому приймаю активну участь. Ми з фронтами завжди дивимось в сторону оптимізації.
Сподіваюсь матеріал виявився корисним і підняв важливі аспекти не тільки для новачків, а й для спеціалістів, які давно з цим працюють.
Моя ціль була показати альтернативу, яка є на ринку і можливо когось надихне змінити Formik => Tanstack

Підписатись на коментарі