2025年5月26日月曜日

【第5回】React Hook Formによるフォーム処理の実践 - 複数メールアドレスの処理

✅ はじめに

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

第5回となる今回は、「React Hook Formを使った複数メールアドレスの入力フォーム」を実装します。useFieldArrayを使用した動的なフォーム要素の追加・削除と、効果的なバリデーション処理を学びます。

📦 使用する技術スタック

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

🚀 実装手順

1. プロジェクトの作成

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

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

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

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

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

3. 複数メール入力フォームの作成

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

import type { FC } from "react"
import { useForm, useFieldArray } from "react-hook-form"
import {
  Box,
  Button,
  Container,
  TextField,
  Typography,
} from "@mui/material"
import { Add, Delete } from "@mui/icons-material"

type FormValues = {
  name: string
  emails: { email: string }[]
}

const AdvancedForm: FC = () => {
  const {
    register,
    control,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    defaultValues: {
      name: "",
      emails: [{ email: "" }],
    },
  })

  const { fields, append, remove } = useFieldArray({
    control,
    name: "emails",
  })

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

  return (
    <Container maxWidth="sm">
      <Box mt={5}>
        <Typography variant="h5" gutterBottom>
          高度なフォーム(React Hook Form + MUI)
        </Typography>
        <form onSubmit={handleSubmit(onSubmit)} noValidate>
          <Box display="flex" flexDirection="column" gap={2}>
            <Box>
              <TextField
                fullWidth
                label="名前"
                {...register("name", { required: "名前は必須です" })}
                error={Boolean(errors.name)}
                helperText={errors.name?.message}
              />
            </Box>
            {fields.map((field, index) => (
              <Box key={field.id}>
                <Box display="flex" alignItems="center" gap={1}>
                  <TextField
                    fullWidth
                    label={`メール ${index + 1}`}
                    {...register(`emails.${index}.email`, {
                      required: "メールは必須です",
                      pattern: {
                        value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
                        message: "メール形式が不正です",
                      },
                    })}
                    error={Boolean(errors.emails?.[index]?.email)}
                    helperText={errors.emails?.[index]?.email?.message}
                  />
                  <Button
                    variant="outlined"
                    color="secondary"
                    onClick={() => remove(index)}
                  >
                    <Delete />
                  </Button>
                </Box>
              </Box>
            ))}
            <Box>
              <Button
                variant="outlined"
                startIcon={<Add />}
                onClick={() => append({ email: "" })}
              >
                メール追加
              </Button>
            </Box>
            <Box mt={2} display="flex" justifyContent="center">
              <Button type="submit" variant="contained" color="primary">
                送信
              </Button>
            </Box>
          </Box>
        </form>
      </Box>
    </Container>
  )
}

export default AdvancedForm

4. App.tsxの更新

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

import { ThemeProvider, createTheme } from '@mui/material'
import AdvancedForm from "./AdvancedForm"
import '@fontsource/roboto/300.css'
import '@fontsource/roboto/400.css'
import '@fontsource/roboto/500.css'
import '@fontsource/roboto/700.css'

const theme = createTheme({
  typography: {
    fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
  },
})

function App() {
  return (
    <ThemeProvider theme={theme}>
      <AdvancedForm />
    </ThemeProvider>
  )
}

export default App

🎯 主要なポイント

1. フォーム状態管理

  • useForm: フォーム全体の状態とバリデーション管理
  • useFieldArray: 動的なフィールド配列の管理
  • 型安全な実装: TypeScriptによる型定義

2. バリデーション機能

  • 必須チェック: 名前とメールアドレスの入力必須
  • メール形式: 正規表現によるメールアドレス形式の検証
  • 視覚的フィードバック: エラーメッセージの即時表示

3. UIコンポーネント

  • TextField: 入力フィールドとエラー表示の統合
  • Button: アイコン付きの操作ボタン
  • 動的なUI: フィールドの追加・削除機能

🔚 まとめ

今回は、React Hook Form の useFieldArray を使って、複数メール入力のフォームを実装しました。 動的なフォーム要素の管理とバリデーション処理を効率的に実装できることが分かりました。

📚 次回予告

【第6回】バリデーション結果をモーダルで表示するReact Hook Form実装