formik vs jotai vs mobx vs react-query vs recoil vs redux vs xstate vs zustand
Reactアプリケーションにおける状態管理とフォーム処理のライブラリ選定ガイド
formikjotaimobxreact-queryrecoilreduxxstatezustand類似パッケージ:

Reactアプリケーションにおける状態管理とフォーム処理のライブラリ選定ガイド

formik、jotai、mobx、react-query、recoil、redux、xstate、zustand は、Reactアプリケーションにおける状態管理やフォーム処理を支援するための代表的なnpmパッケージです。これらのライブラリはそれぞれ異なるアプローチで状態の扱いを解決しており、グローバル状態、ローカル状態、非同期データ、UI状態、フォーム状態、あるいは複雑な状態遷移といった多様な課題に対応しています。たとえば formik はフォーム入力のバリデーションや送信処理に特化し、react-query はサーバーからの非同期データ取得とキャッシュ管理に焦点を当てています。一方で redux や zustand は汎用的なグローバル状態管理を提供し、xstate は有限状態マシン(FSM)による堅牢な状態遷移モデリングを可能にします。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
formik034,310585 kB8441年前Apache-2.0
jotai021,300113 kB21ヶ月前MIT
mobx028,2144.88 MB192日前MIT
react-query050,4802.26 MB1694年前MIT
recoil019,4112.21 MB3204年前MIT
redux061,447290 kB153年前MIT
xstate030,3572.31 MB1248日前MIT
zustand058,87196 kB44日前MIT

React状態管理・フォームライブラリ徹底比較:formik、jotai、mobx、react-query、recoil、redux、xstate、zustand

Reactアプリケーションを開発する上で、状態の扱いは常に重要な課題です。一口に「状態」といっても、UIのトグル、フォーム入力、サーバーから取得したデータ、複雑なワークフローなど、その種類はさまざまです。この記事では、代表的な8つのライブラリ — formik、jotai、mobx、react-query、recoil、redux、xstate、zustand — を、実際の開発シーンを想定しながら技術的に比較します。

📝 フォーム入力の管理:formik vs その他

フォーム処理は多くのアプリで共通の課題ですが、専用ライブラリを使うべきか、それとも汎用状態管理で賄うべきか迷うことがあります。

formik は、フォーム専用のライブラリとして、バリデーション、エラー表示、初期値設定、サブミット処理を一括で提供します。

// formik
import { useFormik } from 'formik';

function MyForm() {
  const formik = useFormik({
    initialValues: { email: '', password: '' },
    validate: (values) => {
      const errors = {};
      if (!values.email) errors.email = '必須です';
      return errors;
    },
    onSubmit: (values) => alert(JSON.stringify(values, null, 2))
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <input name="email" onChange={formik.handleChange} value={formik.values.email} />
      {formik.errors.email && <div>{formik.errors.email}</div>}
      <button type="submit">送信</button>
    </form>
  );
}

一方、zustand や redux でもフォームは実装可能ですが、バリデーションやエラー状態の管理をすべて自前で書く必要があります。そのため、中規模以上のフォームでは formik の方が保守性が高くなります。

// zustand(フォーム用ストア)
import { create } from 'zustand';

const useFormStore = create((set) => ({
  email: '',
  setEmail: (email) => set({ email }),
  // バリデーションロジックは別途実装必要
}));

function MyForm() {
  const { email, setEmail } = useFormStore();
  return <input value={email} onChange={(e) => setEmail(e.target.value)} />;
}

🌐 非同期データ(サーバー状態)の管理:react-query の役割

UI状態とサーバー状態は分けて考えるべきです。react-query は後者に特化しており、以下のような機能を提供します。

  • 自動再取得(stale-while-revalidate)
  • キャッシュ管理
  • ローディング・エラー状態のハンドリング
  • ミューテーション(POST/PUT)
// react-query
import { useQuery, useMutation } from '@tanstack/react-query';

function UserProfile({ userId }) {
  const { data, isLoading } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId)
  });

  const mutation = useMutation({
    mutationFn: updateUser,
    onSuccess: () => queryClient.invalidateQueries(['user', userId])
  });

  if (isLoading) return <div>読み込み中...</div>;
  return <div>{data.name}</div>;
}

redux や zustand でも非同期データは扱えますが、キャッシュの有効期限、再取得のトリガー、並列リクエストの重複回避など、react-query が標準で提供する機能をすべて自前で実装するのは非効率です。

🧠 グローバルUI状態の管理:redux、zustand、jotai、recoil

グローバルなUI状態(例:テーマ、ユーザープロファイル、サイドバーの開閉状態)をどう管理するかは、プロジェクト規模によって変わります。

redux は、厳格なアーキテクチャと豊富なツール(Redux DevTools)が特徴です。

// redux
const setUser = (user) => ({ type: 'SET_USER', payload: user });

const userReducer = (state = null, action) => {
  if (action.type === 'SET_USER') return action.payload;
  return state;
};

// コンポーネント側
const user = useSelector(state => state.user);
const dispatch = useDispatch();
dispatch(setUser({ name: 'Alice' }));

zustand は、同じようなユースケースをより簡潔に実現します。

// zustand
const useStore = create((set) => ({
  user: null,
  setUser: (user) => set({ user })
}));

// コンポーネント側
const { user, setUser } = useStore();

jotai は、原子(atom)単位で状態を分割し、派生状態(computed)も自然に記述できます。

// jotai
import { atom, useAtom } from 'jotai';

const userAtom = atom(null);
const userNameAtom = atom((get) => get(userAtom)?.name ?? 'ゲスト');

function Component() {
  const [user, setUser] = useAtom(userAtom);
  const [name] = useAtom(userNameAtom);
  return <div>Hello, {name}</div>;
}

recoil も同様のアプローチですが、Reactの新機能(Concurrent Rendering)との連携を意識した設計になっています。

// recoil
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';

const userState = atom({ key: 'user', default: null });
const userNameState = selector({
  key: 'userName',
  get: ({ get }) => get(userState)?.name ?? 'ゲスト'
});

function Component() {
  const [user, setUser] = useRecoilState(userState);
  const name = useRecoilValue(userNameState);
  return <div>Hello, {name}</div>;
}

⚡ リアクティブな状態管理:mobx

mobx は、プロキシを使って状態の変更を自動で検知し、依存コンポーネントだけを再レンダリングします。

// mobx
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';

class UserStore {
  user = null;
  constructor() { makeAutoObservable(this); }
  setUser(user) { this.user = user; }
}

const userStore = new UserStore();

const UserProfile = observer(() => {
  return <div>{userStore.user?.name}</div>;
});

このアプローチは、状態の変更が即座にUIに反映されるため、開発体験が非常にスムーズです。ただし、状態の変更が暗黙的に行われるため、デバッグが難しい場合もあります。

🧩 複雑な状態遷移:xstate

有限状態マシン(FSM)は、状態とイベントの関係を明確に定義することで、バグを減らすのに有効です。

// xstate
import { createMachine, interpret } from 'xstate';

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: { on: { TOGGLE: 'active' } },
    active: { on: { TOGGLE: 'inactive' } }
  }
});

const service = interpret(toggleMachine).start();
service.send('TOGGLE'); // inactive → active

Reactコンポーネントでは、@xstate/react の useMachine フックを使って統合します。

import { useMachine } from '@xstate/react';

function Toggle() {
  const [state, send] = useMachine(toggleMachine);
  return (
    <button onClick={() => send('TOGGLE')}>
      {state.value === 'active' ? 'ON' : 'OFF'}
    </button>
  );
}

これは、マルチステップフォームやチャットボット、ゲームロジックなど、状態遷移が複雑な場面で特に有効です。

🔁 状態の更新と再レンダリングの最適化

不要な再レンダリングを避けることは、パフォーマンス向上の鍵です。

  • redux: useSelector で選択した部分だけを監視。ただし、オブジェクトの参照が変わると再レンダリングされるため、メモ化が必要。
  • zustand: ストアの一部を直接選択できるため、不要な再レンダリングが起きにくい。
  • jotai / recoil: Atom単位で依存解決されるため、影響範囲が最小限。
  • mobx: 変更されたプロパティに依存するコンポーネントだけが再レンダリング。

たとえば、ユーザー情報とテーマ設定を別々のAtomで管理すれば、テーマが変わってもユーザー情報を表示するコンポーネントは再レンダリングされません。

🛠️ 非同期処理の統合

  • redux: redux-thunk や redux-saga などのミドルウェアが必要。
  • zustand: 非同期アクションを直接ストアに記述可能。
  • mobx: async/await をそのままアクション内で使用可能(flow ユーティリティも存在)。
  • react-query: 非同期処理がコア機能のため、最もシンプル。
// zustand(非同期)
const useStore = create((set) => ({
  user: null,
  fetchUser: async (id) => {
    const user = await api.getUser(id);
    set({ user });
  }
}));

📦 プロジェクト規模と選定指針

プロジェクト規模推奨ライブラリ
小規模(1〜2人月)zustand + react-query + 必要に応じて formik
中規模(3〜6人月)jotai or recoil + react-query + formik
大規模(6人月以上)redux(DevTools重視) or mobx(リアクティブ重視) + react-query + formik
複雑なワークフローxstate + 他の状態管理ライブラリ併用

💡 最終的なアドバイス

  • フォーム → formik(中規模以上)
  • サーバー状態 → react-query(ほぼ必須)
  • グローバルUI状態 → 小規模なら zustand、中規模以上なら jotai or recoil
  • 複雑な状態遷移 → xstate
  • 既存Redux資産あり → redux を維持
  • リアクティブ志向 → mobx

状態管理は「銀の弾丸」が存在しません。各ライブラリの特性を理解し、プロジェクトの要件に応じて組み合わせるのが最善の戦略です。

選び方: formik vs jotai vs mobx vs react-query vs recoil vs redux vs xstate vs zustand

  • formik:

    formik は、中規模以上のフォームで再利用性・保守性を重視する場合に最適です。バリデーション、エラーハンドリング、サブミット処理、フィールドの追跡などを宣言的に扱えるため、手動でuseStateやuseEffectを組み合わせるよりもコードが整理されます。ただし、単純なフォームではオーバーヘッドになる可能性があるため、小規模な用途では素のReactフックで十分です。

  • jotai:

    jotai は、Reduxのようなグローバル状態管理が必要だが、Context APIの再レンダリング問題を避けたい場合に有効です。原子(atom)単位で状態を分割し、依存関係に基づいて自動的に最適化されるため、大規模アプリでもパフォーマンスを維持しやすいです。TypeScriptとの親和性も高く、シンプルなAPIながら柔軟な構成が可能です。

  • mobx:

    mobx は、クラスベースまたはプロキシベースのリアクティブな状態管理を好むチームに向いています。状態の変更を自動で検知し、依存コンポーネントだけを再レンダリングする仕組みにより、パフォーマンスと開発体験の両立が可能です。ただし、非同期処理や状態の永続化には別途設計が必要で、学習コストがやや高めです。

  • react-query:

    react-query は、サーバーから取得する非同期データ(APIレスポンスなど)のキャッシュ、再取得、エラー処理、バックグラウンド更新を一元管理したい場合に最適です。ローカル状態ではなく「サーバー状態」に特化しているため、UI状態やフォーム状態とは明確に分離すべきです。これにより、データ取得ロジックがコンポーネントから分離され、テストや保守が容易になります。

  • recoil:

    recoil は、AtomとSelectorという概念で状態と派生状態を宣言的に記述できるため、複雑な計算や依存関係を持つUI状態に適しています。React Concurrent Modeとの互換性も考慮されており、将来的なReactの進化にも追随しやすい設計です。ただし、まだ本番環境での長期運用実績が他のライブラリに比べて少ない点に注意が必要です。

  • redux:

    redux は、厳格な単一真実源(single source of truth)と予測可能な状態遷移を求める大規模アプリケーションに向いています。特に時間旅行デバッグや状態の永続化、ミドルウェアによるロギング・非同期処理の拡張が必要な場合に強みを発揮します。ただし、ボイラープレートが多く、小規模プロジェクトでは過剰な設計となる可能性があります。

  • xstate:

    xstate は、複雑な状態遷移(例:マルチステップフォーム、チャットボット、ゲームロジック)を有限状態マシン(FSM)または状態チャートでモデル化したい場合に最適です。状態とイベントの関係が明確になり、バグの原因となる不正な遷移を設計段階で排除できます。ただし、シンプルなUI状態には向かず、学習曲線がやや急です。

  • zustand:

    zustand は、Reduxのようなグローバル状態管理を最小限のボイラープレートで実現したい場合に最適です。Context APIを使わず、内部で自前のストアを管理するため、不要な再レンダリングが起きにくく、パフォーマンスも良好です。また、非同期アクションやミドルウェアも簡単に統合でき、小〜中規模アプリの状態管理に非常に使いやすい選択肢です。

formik のREADME

Formik.js

Build forms in React, without the tears.


Stable Release Blazing Fast gzip size license Discord

Visit https://formik.org to get started with Formik.

Organizations and projects using Formik

List of organizations and projects using Formik

Authors

Contributing

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!

Contributors

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!

Related

  • TSDX - Zero-config CLI for TypeScript used by this repo. (Formik's Rollup configuration as a CLI)

Apache 2.0 License.