✅ はじめに
こんにちは!このシリーズでは、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:ステップフォームの実装