final-form、formik、react-final-form、redux-formはすべてReactアプリケーションにおけるフォーム管理を支援するライブラリですが、設計思想や統合方法に大きな違いがあります。final-formはフレームワーク非依存のコアロジックを提供し、react-final-formはそのReact向けバインディングです。formikはReact専用に最適化された高レベルAPIを提供し、直感的な使いやすさが特徴です。一方、redux-formはReduxストアにフォーム状態を保存する設計を採用していましたが、現在は非推奨となっています。これらのライブラリは、フォームの状態管理、バリデーション、サブミット処理、パフォーマンス最適化といった共通の課題に対して、それぞれ異なるアプローチで解決策を提供しています。
Reactアプリケーションでフォームを扱う際、状態管理やバリデーション、サブミット処理など多くの課題があります。final-form、formik、react-final-form、redux-formはそれぞれ異なるアプローチでこれらの課題に取り組んでいますが、設計思想や統合方法には大きな違いがあります。この記事では、実際の開発現場での使い勝手を中心に、各ライブラリの技術的特徴を比較します。
final-form は フレームワーク非依存の純粋なJavaScriptライブラリ です。Reactだけでなく、VueやAngularなど他のUIフレームワークでも利用可能です。内部でフォーム状態(値、エラー、dirtyフラグなど)を管理し、サブスクライブ可能なオブジェクトを提供します。
// final-form: 純粋なJS API
import { createForm } from 'final-form';
const form = createForm({
onSubmit: values => console.log(values)
});
form.subscribe(state => {
console.log('Current values:', state.values);
}, { values: true });
formik は React専用に最適化された高レベルAPI を提供します。useFormikフックや<Formik>コンポーネントを通じて、フォームロジックを宣言的に記述できます。状態管理からバリデーション、サブミット処理まで一貫したインターフェースを提供します。
// formik: React専用API
import { useFormik } from 'formik';
function MyForm() {
const formik = useFormik({
initialValues: { email: '' },
onSubmit: values => alert(JSON.stringify(values, null, 2))
});
return (
<form onSubmit={formik.handleSubmit}>
<input
name="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
}
react-final-form は final-form の React向けバインディング です。final-formのコアロジックを活かしつつ、Reactコンポーネントとして使いやすいようにラップしています。
// react-final-form: final-formのReactバインディング
import { Form, Field } from 'react-final-form';
const MyForm = () => (
<Form
onSubmit={values => console.log(values)}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email">
{({ input }) => <input {...input} />}
</Field>
<button type="submit">Submit</button>
</form>
)}
/>
);
redux-form は Reduxストアにフォーム状態を保存する設計 を採用しています。すべてのフォームデータがReduxのstateツリー内に存在し、通常のReduxアクションで操作されます。
// redux-form: Redux統合
import { reduxForm, Field } from 'redux-form';
const MyForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email" component="input" />
<button type="submit">Submit</button>
</form>
);
export default reduxForm({
form: 'myForm' // Redux state内のフォーム識別子
})(MyForm);
⚠️ 重要:
redux-formは公式npmページで**非推奨(deprecated)**と明記されています。新規プロジェクトでの使用は避けてください。
各ライブラリはフォーム状態を異なる場所に保存します。これはアプリケーション全体のアーキテクチャに大きな影響を与えます。
final-form と react-final-form は フォームインスタンス内に状態を保持 します。ReduxやContext APIとは独立しており、メモリ内で完結します。
// final-form: 内部状態管理
const form = createForm({ /* ... */ });
// 状態はformインスタンス内に閉じている
formik も同様に コンポーネントのローカル状態(useState相当)に保存 します。ただし、enableReinitializeオプションを使うことで外部propsからの再初期化も可能です。
// formik: ローカル状態
const formik = useFormik({
initialValues: props.initialData, // 外部から初期値を受け取れる
enableReinitialize: true // props変更時に再初期化
});
redux-form は Reduxストアの特定のパスに状態を保存 します。例えばform.myForm.valuesのようにアクセス可能で、他のReduxアクションからも操作できます。
// redux-form: Reduxストア内に保存
// store.getState().form.myForm.values
この違いは以下のような実践的な影響をもたらします:
final-form/formikはフォーム外の再レンダリングの影響を受けにくいredux-formはRedux DevToolsで状態を追跡できるが、ストアが肥大化しやすいfinal-form/formikはReduxのモック不要で単体テストが容易バリデーションはフォームライブラリの重要な機能ですが、各ライブラリのアプローチは異なります。
final-form と react-final-form は 同期・非同期バリデーション関数を直接渡す 方式を採用しています。
// final-form: バリデーション関数
const validate = values => {
const errors = {};
if (!values.email) errors.email = 'Required';
return errors;
};
const form = createForm({ validate });
formik も同様に validate関数またはYupスキーマ をサポートしています。
// formik: Yupによるバリデーション
import * as Yup from 'yup';
const formik = useFormik({
validationSchema: Yup.object({
email: Yup.string().required('Required')
})
});
redux-form も validateプロップとして関数を受け取る 方式です。
// redux-form: バリデーション
const validate = values => {
const errors = {};
if (!values.email) errors.email = 'Required';
return errors;
};
export default reduxForm({
form: 'myForm',
validate
})(MyForm);
共通点として、すべてのライブラリがフィールド単位のリアルタイムバリデーションをサポートしていますが、formikはYupとの統合によりより宣言的なバリデーション記述が可能です。
複雑なフォームでは、1つのフィールドの変更が全体を再レンダリングしてしまう問題が発生します。各ライブラリはこの問題に対処するための仕組みを提供しています。
final-form は 細かい粒度でのサブスクリプション をサポートしています。必要な状態だけを監視することで、不要な更新を回避できます。
// final-form: 細かいサブスクリプション
form.subscribe(state => {
// emailフィールドの値だけを監視
}, { value: true, name: 'email' });
react-final-form はこの機能をReactコンポーネントとして提供します。<Field>コンポーネントはデフォルトで対応フィールドのみを再レンダリングします。
// react-final-form: 自動的な最適化
<Field name="email">
{({ input, meta }) => (
// emailフィールドの変更時のみ再レンダリング
<input {...input} />
)}
</Field>
formik は useFieldフック を使って個別のフィールド状態にアクセスできますが、デフォルトではフォーム全体の状態変更で再レンダリングが発生します。
// formik: useFieldで最適化
const EmailField = () => {
const [field, meta] = useField('email');
// フォーム全体ではなくemailフィールドのみの変更で更新
return <input {...field} />;
};
redux-form はReduxの性質上、connectのメモ化やreselectの使用が必要 です。適切に最適化しないと、小さな変更でも大量の再レンダリングが発生します。
// redux-form: 手動最適化が必要
import { connect } from 'react-redux';
const mapStateToProps = (state, ownProps) => ({
// reselectなどでメモ化が必要
email: getFormValues(state).email
});
複雑なUI(日付ピッカー、オートコンプリートなど)をフォームに統合する際、カスタムフィールドコンポーネントの作成が重要になります。
react-final-form は render propパターン を採用しており、任意のコンポーネントにフォーム機能を注入できます。
// react-final-form: カスタムフィールド
const DatePickerField = ({ name }) => (
<Field name={name}>
{({ input }) => (
<DatePicker
selected={input.value}
onChange={input.onChange}
/>
)}
</Field>
);
formik は useFieldフック を使って同じ目的を達成します。
// formik: カスタムフィールド
const DatePickerField = ({ name }) => {
const [field, , helpers] = useField(name);
return (
<DatePicker
selected={field.value}
onChange={helpers.setValue}
/>
);
};
redux-form は Fieldコンポーネントのcomponentプロップ でカスタムコンポーネントを指定できます。
// redux-form: カスタムフィールド
const renderDatePicker = ({ input }) => (
<DatePicker
selected={input.value}
onChange={input.onChange}
/>
);
<Field name="date" component={renderDatePicker} />
final-form(スタンドアロン版)は直接DOM操作が必要になるため、Reactでのカスタムフィールド作成には向いていません。
formik が最もバランスの良い選択肢です。学習コストが低く、豊富な機能と良好なTypeScriptサポートを備えています。react-final-form が優れた選択肢です。細かい再レンダリング制御と軽量なコアが、複雑なフォームでもスムーズな動作を実現します。redux-formは使用しないでください。代わりにformikまたはreact-final-formを採用し、必要に応じてReduxとの連携を手動で実装してください。final-form コアを使用し、各UIフレームワーク向けに独自のバインディングを作成します。| ライブラリ | 主な特徴 | 推奨用途 |
|---|---|---|
final-form | フレームワーク非依存、軽量コア | 複数フレームワーク対応が必要なケース |
formik | React専用、高レベルAPI、Yup統合 | 多くのReactプロジェクトに最適 |
react-final-form | final-formのReactバインディング、細かい最適化 | パフォーマンスが重要な大規模フォーム |
redux-form | 非推奨、Redux統合 | 新規プロジェクトでは使用しない |
現代のReact開発においては、formik と react-final-form が主要な選択肢となります。プロジェクトの規模、パフォーマンス要件、チームの熟悉度を考慮して選択してください。
formikを選ぶべきは、大多数のReactプロジェクトにおいてバランスの取れたソリューションが欲しい場合です。学習コストが低く、直感的なAPI、豊富な機能(Yupとのバリデーション統合、Field配列サポートなど)、優れたTypeScriptサポートを備えています。小規模から中規模のアプリケーションで特に効果を発揮し、開発速度と保守性の両立が可能です。
final-formを選ぶべきは、React以外のUIフレームワーク(Vue、Angularなど)でも同じフォームロジックを使いたい場合です。純粋なJavaScript APIを提供するため、フレームワーク非依存のアーキテクチャが必要なプロジェクトに最適です。ただし、React専用の機能(フック、コンポーネント統合)は提供しないため、Reactでの開発効率はreact-final-formやformikに劣ります。
redux-formは公式に非推奨(deprecated)とされているため、新規プロジェクトでは絶対に使用しないでください。既存のReduxアプリケーションで使用されている場合は、formikまたはreact-final-formへの移行を検討すべきです。Reduxストアにフォーム状態を保存する設計は、ストアの肥大化やパフォーマンス問題を引き起こすことが多く、現代のReact開発のベストプラクティスから外れています。
react-final-formを選ぶべきは、大規模で複雑なフォームがあり、パフォーマンス(不要な再レンダリングの回避)が重要な場合です。final-formの軽量で効率的なコアを利用しつつ、Reactコンポーネントとして使いやすいインターフェースを提供します。細かい粒度での再レンダリング制御が必要な高度なユースケースに最適です。
Visit https://formik.org to get started with Formik.
List of organizations and projects using Formik
This monorepo uses yarn, so to start you'll need the package manager installed.
To run E2E tests you'll also need Playwright set up, which can be done locally via npx playwright install. Afterward, run yarn start:app and in a separate tab run yarn e2e:ui to boot up the test runner.
When you're done with your changes, we use changesets to manage release notes. Run yarn changeset to autogenerate notes to be appended to your pull request.
Thank you!
Formik is made with <3 thanks to these wonderful people (emoji key):
Jared Palmer 💬 💻 🎨 📖 💡 🤔 👀 ⚠️ | Ian White 💬 🐛 💻 📖 🤔 👀 | Andrej Badin 💬 🐛 📖 | Adam Howard 💬 🐛 🤔 👀 | Vlad Shcherbin 💬 🐛 🤔 | Brikou CARRE 🐛 📖 | Sam Kvale 🐛 💻 ⚠️ |
|---|---|---|---|---|---|---|
Jon Tansey 🐛 💻 | Tyler Martinez 🐛 📖 | Tobias Lohse 🐛 💻 |
This project follows the all-contributors specification. Contributions of any kind welcome!