formik、react-final-form、react-hook-form、redux-form、vee-validate、yup はすべて、Reactアプリケーションにおけるフォームの状態管理や入力バリデーションを支援するためのライブラリです。これらのパッケージは、フォームの複雑さを軽減し、開発者が再利用可能なコンポーネントや堅牢なバリデーションロジックを構築できるように設計されています。ただし、アーキテクチャやパフォーマンス特性、API設計、依存関係の有無などに大きな違いがあります。たとえば、yup は純粋なバリデーションスキーマ定義ライブラリであり、UIフレームワークとは独立していますが、他の5つはReactフォームとの統合を前提としています。また、redux-form は公式に非推奨とされており、新規プロジェクトでの使用は避けるべきです。
Reactアプリケーションでフォームを扱う際、単なる <input> の集合を超えて、状態管理・バリデーション・エラーハンドリング・サブミット制御などを一貫して扱う必要があります。この領域には複数の成熟したライブラリが存在しますが、それぞれ設計哲学やパフォーマンス特性、APIスタイルが大きく異なります。本稿では、6つの主要パッケージ — formik、react-final-form、react-hook-form、redux-form、vee-validate、yup — を、実践的な観点から比較します。
💡 注:
redux-formは npmページ および GitHubリポジトリ で明確に 非推奨(deprecated) とされており、新規プロジェクトでの使用は避けてください。以下では参考のために言及しますが、選択肢としては除外すべきです。
フォームライブラリの核心は、「フォームの状態をどのように管理するか」にあります。この点で、各ライブラリは大きく2つのアプローチに分かれます。
formik と react-final-form:Controlled ベースこれらは、フォームの全状態をReactのstate(または内部ストア)で管理し、各入力要素を「controlled component」として扱います。つまり、値の変更ごとにstateが更新され、再レンダリングが発生します。
// formik: Controlled コンポーネント
import { useFormik } from 'formik';
function MyForm() {
const formik = useFormik({
initialValues: { email: '' },
onSubmit: values => alert(JSON.stringify(values)),
validate: values => {
const errors = {};
if (!values.email) errors.email = '必須です';
return errors;
}
});
return (
<form onSubmit={formik.handleSubmit}>
<input
name="email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">送信</button>
</form>
);
}
// react-final-form: render props + controlled
import { Form, Field } from 'react-final-form';
const MyForm = () => (
<Form
onSubmit={values => alert(JSON.stringify(values))}
validate={values => {
const errors = {};
if (!values.email) errors.email = '必須です';
return errors;
}}
render={({ handleSubmit, pristine, invalid }) => (
<form onSubmit={handleSubmit}>
<Field name="email">
{({ input, meta }) => (
<>
<input {...input} />
{meta.touched && meta.error && <div>{meta.error}</div>}
</>
)}
</Field>
<button type="submit" disabled={pristine || invalid}>送信</button>
</form>
)}
/>
);
react-hook-form:Uncontrolled ベース一方、react-hook-form は、Reactの「uncontrolled component」モデルを活用します。入力値はDOM内に保持され、必要なときだけ読み出されます。これにより、ユーザーのタイピング中に再レンダリングが発生せず、パフォーマンスが大幅に向上します。
// react-hook-form: uncontrolled コンポーネント
import { useForm } from 'react-hook-form';
function MyForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
return (
<form onSubmit={handleSubmit(data => alert(JSON.stringify(data)))}>
<input
{...register('email', { required: '必須です' })}
/>
{errors.email && <div>{errors.email.message}</div>}
<button type="submit">送信</button>
</form>
);
}
vee-validate:柔軟なアプローチvee-validate は、React向けに提供される useField や useForm フックを通じて、controlled/uncontrolled の両方をサポートしますが、基本的にはcontrolled方式を推奨しています。
// vee-validate: Vue風のルールベース
import { useField, useForm } from 'vee-validate';
import * as yup from 'yup';
const schema = yup.object({
email: yup.string().required('必須です')
});
function MyForm() {
const { handleSubmit } = useForm({
validationSchema: schema
});
const { value: email, errorMessage, handleChange } = useField('email');
return (
<form onSubmit={handleSubmit(values => alert(JSON.stringify(values)))}>
<input
name="email"
value={email}
onChange={handleChange}
/>
{errorMessage && <div>{errorMessage}</div>}
<button type="submit">送信</button>
</form>
);
}
yup:バリデーション専用yup はフォームライブラリではなく、バリデーションスキーマを定義するための独立ライブラリです。上記のどのフォームライブラリとも組み合わせて使えます。
// yup: 純粋なバリデーションスキーマ
import * as yup from 'yup';
const schema = yup.object({
email: yup.string().email().required()
});
// 例:formik と組み合わせる
schema.validate({ email: 'test@example.com' })
.then(valid => console.log(valid))
.catch(err => console.log(err.message));
redux-form:非推奨のcontrolled方式歴史的な参考として、redux-form はReduxストアにフォーム状態を保存するcontrolled方式でした。しかし、現在は非推奨です。
// redux-form (非推奨)
import { reduxForm, Field } from 'redux-form';
const MyForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email" component="input" />
<button type="submit">送信</button>
</form>
);
export default reduxForm({ form: 'myForm' })(MyForm);
フォームのパフォーマンスは、主に「不要な再レンダリングがどれだけ少ないか」で決まります。
react-hook-form は、uncontrolled方式により、入力中の再レンダリングがほぼゼロです。これは大規模フォームやモバイル環境で顕著な利点になります。formik は、デフォルトではフィールド変更ごとに全体が再レンダリングされます。<Field> コンポーネントや useField を使えば部分最適化は可能ですが、注意深い設計が必要です。react-final-form は、render props の粒度で再レンダリングを制御でき、パフォーマンスは良好です。ただし、親コンポーネントの再レンダリングが子に波及する可能性があります。vee-validate は、フィールド単位で状態を管理するため、他のcontrolledライブラリより再レンダリングは抑制されますが、react-hook-form ほどではありません。すべてのライブラリが同期バリデーションをサポートしていますが、記述方法が異なります。
formik:validate 関数または validationSchema(Yupと連携)react-final-form:validate 関数react-hook-form:ビルトインルール(required, minLength など)または yup/zod との統合vee-validate:ルールベース(required, email)または yup スキーマyup:スキーマ定義のみメールアドレスの重複チェックなど、API呼び出しを伴うバリデーションが必要な場合:
formik:validate 関数内で async/await を使用可能react-final-form:asyncValidate オプションありreact-hook-form:register の validate オプションで非同期関数を指定可能vee-validate:setFieldError と組み合わせて非同期処理を実装// react-hook-form での非同期バリデーション例
const { register } = useForm();
<input
{...register('email', {
validate: async (value) => {
const response = await fetch(`/api/check-email?email=${value}`);
const exists = await response.json();
return exists ? 'このメールは既に登録されています' : true;
}
})}
/>
react-hook-form:型定義が非常に洗練されており、フォーム値の型安全性が最高レベルです。formik:v2以降でTypeScriptサポートが改善されましたが、一部のAPIで型のゆるさがあります。yup:スキーマから型を推論する inferType が利用可能で、他のライブラリとの連携で強力です。react-final-form:型サポートはありますが、render props のため型推論がやや煩雑です。vee-validate:TypeScript対応は良好ですが、React向けの型定義はVue版ほど成熟していません。react-hook-form:依存なし。軽量で自己完結。formik:immer に依存(内部で不変性操作に使用)。yup:@babel/runtime などに依存。react-final-form:final-form というコアパッケージに依存。vee-validate:ルールセットが大きいとバンドルサイズが増加。react-hook-formユーザー体験を最優先するモダンアプリでは、react-hook-form が最もバランスの取れた選択です。特に、リアルタイムバリデーションや多数の入力フィールドがある場合にその真価を発揮します。
formikフォーム内に条件付きフィールドや動的リスト(例:複数の住所入力)が多い場合、formik の useFieldArray や setFieldValue が便利です。ただし、再レンダリングの最適化には注意が必要です。
react-final-formReduxを使っているレガシーアプリで、フォームだけを置き換えたい場合に有効です。ただし、新規プロジェクトでは推奨しません。
yup + 任意のフォームライブラリサーバーサイドとクライアントサイドで同じバリデーションルールを使いたい場合、yup スキーマを共通化すると保守性が向上します。
redux-form は避けるredux-form はメンテナンスされておらず、セキュリティリスクや互換性問題の原因になります。既存コードがある場合は、react-hook-form への移行を計画してください。
| ライブラリ | 推奨用途 | パフォーマンス | 学習コスト | 状態管理方式 |
|---|---|---|---|---|
react-hook-form | 新規プロジェクト、高パフォーマンス要件 | ⭐⭐⭐⭐⭐ | 低 | uncontrolled |
formik | 複雑なフォームロジック | ⭐⭐☆ | 中 | controlled |
react-final-form | レガシーReduxアプリのフォーム置き換え | ⭐⭐⭐ | 中〜高 | controlled |
vee-validate | ルールベースのバリデーション重視 | ⭐⭐⭐ | 中 | controlled |
yup | バリデーションスキーマの共通化 | N/A | 低 | N/A(補助ライブラリ) |
redux-form | 使用禁止 | ⭐ | — | controlled(非推奨) |
react-hook-form を試す。パフォーマンス、DX、TypeScriptサポートのバランスが最良です。yup を組み合わせる。これにより、テスト容易性と再利用性が向上します。redux-form は過去の遺物。見つけたら、移行計画を立てましょう。フォームはユーザー体験の要です。適切なツールを選ぶことで、開発速度とアプリの品質を同時に高めることができます。
react-hook-form は、パフォーマンスと開発体験を重視する現代のReactプロジェクトに強く推奨されます。uncontrolled コンポーネントを活用し、再レンダリングを劇的に削減します。TypeScriptサポートも優れており、シンプルなAPIで直感的に扱えます。ただし、完全な uncontrolled 方式のため、一部の高度な制御ロジックには工夫が必要です。
yup は、フォームライブラリではなく、純粋なJavaScriptオブジェクトのバリデーションスキーマ定義ツールです。formik や react-hook-form と組み合わせて使われることが多く、型安全で宣言的なバリデーションロジックを実現できます。単体ではフォーム状態管理機能を持たないため、必ず他のフォームライブラリと併用してください。
formik は、中規模から大規模なフォームで柔軟性と豊富な機能(フィールド配列、ネストされたオブジェクト、カスタムフックなど)が必要な場合に適しています。Context API を内部で使い、Redux などの外部ステート管理に依存しないため、導入が簡単です。ただし、高頻度の再レンダリングが発生しやすい点に注意が必要です。
vee-validate は、Vue.js由来の設計思想を持ちつつReactでも利用可能なバリデーション中心のライブラリです。ルールベースのバリデーションやi18n対応が強みで、UIコンポーネントとの疎結合が可能です。ただし、Reactエコシステムではややマイナーであり、コミュニティサポートは他より限定的です。
react-final-form は、高性能かつ宣言的なフォーム管理を求めるプロジェクトに最適です。render props パターンを採用しており、不要な再レンダリングを最小限に抑えられます。ただし、render props の記法は現代のReact開発ではやや古く感じられ、学習コストが若干高くなります。
redux-form は公式に非推奨(deprecated)とされており、新規プロジェクトでは絶対に使用すべきではありません。既存のReduxベースのレガシーアプリケーションでしか使われず、メンテナンスも停止されています。代わりに react-hook-form や formik への移行を検討してください。
Get started | API | Form Builder | FAQs | Examples
npm install react-hook-form
import { useForm } from 'react-hook-form';
function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register('firstName')} />
<input {...register('lastName', { required: true })} />
{errors.lastName && <p>Last name is required.</p>}
<input {...register('age', { pattern: /\d+/ })} />
{errors.age && <p>Please enter number for age.</p>}
<input type="submit" />
</form>
);
}
We’re incredibly grateful to these kind and generous sponsors for their support!
Thank you to our previous sponsors for your generous support!
Thanks go to all our backers! [Become a backer].
Thanks go to these wonderful people! [Become a contributor].
Documentation website supported and backed by Vercel