2025年5月26日月曜日

【第7回】React Hook Form × MUI:フォームをコンポーネント単位で分割する設計手法

# 【第7回】React Hook Form × MUI:フォームをコンポーネント単位で分割する設計手法

✅ はじめに

こんにちは!このシリーズでは、React + Vite + TypeScript + MUIを使ってモダンなWebアプリを開発する方法をステップバイステップで解説しています。

第7回となる今回は、「フォームをコンポーネント単位で分割する設計手法」について解説します。前回作成したフォームをベースに、入力フィールドを個別のコンポーネントに分割し、保守性と再利用性を高める実装方法を学んでいきます。

📦 使用する技術スタック

ツール バージョン 説明
React Hook Form v7.x 高性能なフォームバリデーションライブラリ
MUI v5.x マテリアルデザインベースのUIコンポーネント
TypeScript 5.x 型安全なJavaScript
Vite 5.x 高速な開発環境とビルドツール

🚀 実装手順

1. プロジェクトの作成

まずは、Viteを使って新しいプロジェクトを作成します:

npm create vite@latest react-form-components -- --template react-ts
cd react-form-components
npm install

2. 必要なパッケージのインストール

MUIとReact Hook Formをインストールします:

npm install @mui/material @emotion/react @emotion/styled @mui/icons-material react-hook-form

3. プロジェクト構成

今回のプロジェクトは以下のような構成になります:

src/
  ├── components/
  │   ├── NameField.tsx
  │   └── EmailField.tsx
  ├── FormComponent.tsx
  └── App.tsx

4. 入力フィールドコンポーネントの作成

まず、src/components/NameField.tsxを作成します:

import { TextField } from "@mui/material";
import { useForm } from "react-hook-form";
import type { FC } from "react";

type Props = {
  register: ReturnType<typeof useForm>["register"];
  error?: string;
};

const NameField: FC<Props> = ({ register, error }) => {
  return (
    <TextField
      fullWidth
      label="名前"
      {...register("name", { required: "名前は必須です" })}
      error={Boolean(error)}
      helperText={error}
    />
  );
};

export default NameField;

次に、src/components/EmailField.tsxを作成します:

import { TextField } from "@mui/material";
import { useForm } from "react-hook-form";
import type { FC } from "react";

type Props = {
  register: ReturnType<typeof useForm>["register"];
  error?: string;
};

const EmailField: FC<Props> = ({ register, error }) => {
  return (
    <TextField
      fullWidth
      label="メール"
      {...register("email", {
        required: "メールは必須です",
        pattern: {
          value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
          message: "メール形式が不正です",
        },
      })}
      error={Boolean(error)}
      helperText={error}
    />
  );
};

export default EmailField;

5. メインフォームコンポーネントの作成

src/FormComponent.tsxを作成します:

import { useForm, type FieldValues, type UseFormRegister } from "react-hook-form";
import { Box, Button, Container, Typography } from "@mui/material";
import NameField from "./components/NameField";
import EmailField from "./components/EmailField";
import type { FC } from "react";

type FormValues = {
  name: string;
  email: string;
};

const FormComponent: FC = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>();

  const onSubmit = (data: FormValues) => {
    console.log("送信データ:", data);
    alert(JSON.stringify(data, null, 2));
  };

  return (
    <Container maxWidth="sm">
      <Box mt={5}>
        <Typography variant="h5" gutterBottom>
          コンポーネント分割フォーム
        </Typography>
        <form onSubmit={handleSubmit(onSubmit)} noValidate>
          <Box display="flex" flexDirection="column" gap={2}>
            <NameField
              register={register as unknown as UseFormRegister<FieldValues>}
              error={errors.name?.message}
            />
            <EmailField
              register={register as unknown as UseFormRegister<FieldValues>}
              error={errors.email?.message}
            />
            <Box textAlign="center">
              <Button type="submit" variant="contained" color="primary">
                送信
              </Button>
            </Box>
          </Box>
        </form>
      </Box>
    </Container>
  );
};

export default FormComponent;

6. App.tsxの更新

最後に、src/App.tsxを更新してフォームを表示します:

import FormComponent from "./FormComponent";
import type { FC } from "react";

const App: FC = () => {
  return <FormComponent />;
};

export default App;

🎯 主要なポイント

1. コンポーネント設計

  • 単一責任の原則: 各コンポーネントは1つの役割に特化
  • 再利用性: 入力フィールドを独立したコンポーネントとして実装
  • 型安全性: TypeScriptによる厳密な型チェック

2. Props設計

  • register関数: React Hook Formの登録関数を適切に受け渡し
  • エラー処理: エラーメッセージを親から子へ伝播
  • 型定義: 明確なPropsの型定義による安全性確保

3. フォーム管理

  • 状態管理: useFormによる一元的なフォーム状態管理
  • バリデーション: 各フィールドでの個別バリデーション定義
  • 送信処理: handleSubmitによる統一的な送信処理

🔚 まとめ

今回は、React Hook Formを使用したフォームをコンポーネント単位に分割する設計手法について学びました。この設計により、以下のメリットが得られます:

  • 保守性の向上: 各コンポーネントが独立して管理可能
  • 🔄 再利用性の向上: 共通の入力フィールドを別のフォームでも使用可能
  • 📝 コードの可読性向上: 責任範囲が明確で理解しやすい構造

📚 次回予告

【第8回】React Hook Form × MUI:ステップフォームの実装