Reactで作るTODOアプリ開発シリーズ第4回
この記事では
Reactでのイベントハンドリングとフォーム処理の実装方法
を学習していきます。
前回はuseStateを使った状態管理について学習しました。今回は、ユーザーの操作を受け取り、アプリケーションと対話できるようにするためのイベントハンドリングとフォーム処理について学んでいきましょう。
ユーザーがボタンをクリックしたり、フォームに入力したりしたときの処理を適切に実装することで、より使いやすいアプリケーションを作ることができます。
- イベントハンドラーの書き方と基本概念
- フォームの制御(制御されたコンポーネント)
- 入力値の検証とバリデーション
- TODO作成フォームの実装
- エラーハンドリングの基礎

イベントハンドリングを理解することで、ユーザーの操作に応じて動的に変化するインタラクティブなアプリケーションを作ることができるようになります!






TODOアプリ開発シリーズのまとめ記事はこちら
前回までのコード
第3回で作成したコードを確認しましょう。以下のファイル構成になっています。
ファイル構造
todo-app/
├── src/
│ ├── App.js
│ ├── App.css
│ ├── TodoItem.js
│ ├── TodoList.js
│ └── TodoHeader.js
App.js
import React, { useState } from 'react';
import './App.css';
import TodoHeader from './TodoHeader';
import TodoList from './TodoList';
function App() {
const [todos, setTodos] = useState([
{ id: 1, text: "Reactを学ぶ", completed: false, priority: "high" },
{ id: 2, text: "TODOアプリを作る", completed: true, priority: "medium" },
{ id: 3, text: "データベース連携", completed: false, priority: "low" },
{ id: 4, text: "デプロイする", completed: false, priority: "medium" }
]);
const completedCount = todos.filter(todo => todo.completed).length;
// TODOの完了状態を切り替える関数
const toggleTodo = (id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
};
return (
<div className="App">
<TodoHeader
title="React TODOアプリ"
totalCount={todos.length}
completedCount={completedCount}
/>
<TodoList todos={todos} onToggle={toggleTodo} />
</div>
);
}
export default App;
TodoList.js
import React from 'react';
import TodoItem from './TodoItem';
function TodoList({ todos, onToggle }) {
return (
<div className="todo-list">
{todos.map((todo) => (
<TodoItem
key={todo.id}
id={todo.id}
text={todo.text}
completed={todo.completed}
priority={todo.priority}
onToggle={onToggle}
/>
))}
</div>
);
}
export default TodoList;
TodoItem.js
import React from 'react';
function TodoItem({ id, text, completed, priority = "medium", onToggle }) {
return (
<div className={`todo-item priority-${priority}`}>
<input
type="checkbox"
checked={completed}
onChange={() => onToggle(id)}
/>
<span style={{
textDecoration: completed ? 'line-through' : 'none'
}}>
{text}
</span>
</div>
);
}
export default TodoItem;
イベントハンドリングの基本
実際に手を動かす前に、まずは必要な知識について解説をしていきます。
Reactでのイベントハンドリングは、HTMLのイベント処理と似ていますが、いくつかの重要な違いがあります。
基本的なイベントハンドラー
import React, { useState } from 'react';
function EventExample() {
const [count, setCount] = useState(0);
// クリックイベントのハンドラー
const handleClick = () => {
setCount(count + 1);
};
// マウスオーバーイベントのハンドラー
const handleMouseOver = () => {
console.log('マウスが上に来ました');
};
return (
<div>
<p>カウント: {count}</p>
<button
onClick={handleClick}
onMouseOver={handleMouseOver}
>
クリックしてください
</button>
</div>
);
}
イベントオブジェクトの使用
イベントハンドラーは、イベントオブジェクトを引数として受け取ることができます。
function EventObjectExample() {
const handleClick = (event) => {
console.log('クリックされた要素:', event.target);
console.log('クリックされた位置:', event.clientX, event.clientY);
};
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
console.log('Enterキーが押されました');
}
};
return (
<div>
<button onClick={handleClick}>
クリックしてイベント情報を確認
</button>
<input
type="text"
onKeyPress={handleKeyPress}
placeholder="Enterキーを押してください"
/>
</div>
);
}
イベントの伝播と停止
Reactでは、イベントの伝播(バブリング)を制御することができます。
function EventPropagationExample() {
const handleParentClick = () => {
console.log('親要素がクリックされました');
};
const handleChildClick = (event) => {
console.log('子要素がクリックされました');
event.stopPropagation(); // イベントの伝播を停止
};
return (
<div
onClick={handleParentClick}
style={{ padding: '20px', border: '1px solid black' }}
>
親要素
<button onClick={handleChildClick}>
子要素(クリックしても親に伝播しません)
</button>
</div>
);
}
フォームの制御(制御されたコンポーネント)
Reactでは、フォームの値を状態で管理することで、入力値を完全に制御できます。これを「制御されたコンポーネント」と呼びます。
基本的な制御されたコンポーネント
import React, { useState } from 'react';
function ControlledForm() {
const [inputValue, setInputValue] = useState('');
const handleChange = (event) => {
setInputValue(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault(); // フォームのデフォルト動作を防ぐ
console.log('送信された値:', inputValue);
setInputValue(''); // 送信後にフォームをクリア
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder="何か入力してください"
/>
<button type="submit">送信</button>
</form>
);
}
複数の入力フィールドの管理
複数の入力フィールドがある場合は、オブジェクトで状態を管理します。
function MultiFieldForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const handleChange = (event) => {
const { name, value } = event.target;
setFormData(prevData => ({
...prevData,
[name]: value
}));
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('送信されたデータ:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">名前:</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="email">メール:</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="message">メッセージ:</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
/>
</div>
<button type="submit">送信</button>
</form>
);
}
入力値の検証とバリデーション
ユーザーの入力値を検証することで、より安全で使いやすいアプリケーションを作ることができます。
基本的なバリデーション
function ValidationExample() {
const [inputValue, setInputValue] = useState('');
const [error, setError] = useState('');
const handleChange = (event) => {
const value = event.target.value;
setInputValue(value);
// バリデーション
if (value.length < 3) {
setError('3文字以上入力してください');
} else if (value.length > 20) {
setError('20文字以下で入力してください');
} else {
setError('');
}
};
const handleSubmit = (event) => {
event.preventDefault();
if (error) {
alert('入力内容にエラーがあります');
return;
}
console.log('送信:', inputValue);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder="3-20文字で入力"
/>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button type="submit" disabled={!!error}>
送信
</button>
</form>
);
}
リアルタイムバリデーション
入力中にリアルタイムでバリデーションを行う例です。
function RealTimeValidation() {
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState('');
const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const handleEmailChange = (event) => {
const value = event.target.value;
setEmail(value);
if (value && !validateEmail(value)) {
setEmailError('有効なメールアドレスを入力してください');
} else {
setEmailError('');
}
};
return (
<div>
<input
type="email"
value={email}
onChange={handleEmailChange}
placeholder="メールアドレスを入力"
/>
{emailError && <p style={{ color: 'red' }}>{emailError}</p>}
</div>
);
}
実装手順
ここからは実際に手を動かして、TODOアプリにフォーム機能を追加していきましょう。
1. TodoFormコンポーネントの作成
src/TodoForm.js
ファイルを新規作成し、以下のコードを記述してください。
import React, { useState } from 'react';
function TodoForm({ onAddTodo }) {
const [formData, setFormData] = useState({
text: '',
priority: 'medium'
});
const [error, setError] = useState('');
const handleChange = (event) => {
const { name, value } = event.target;
setFormData(prevData => ({
...prevData,
[name]: value
}));
// エラーをクリア
if (error) {
setError('');
}
};
const handleSubmit = (event) => {
event.preventDefault();
// バリデーション
if (!formData.text.trim()) {
setError('TODOの内容を入力してください');
return;
}
if (formData.text.length > 100) {
setError('TODOの内容は100文字以下で入力してください');
return;
}
// 新しいTODOを作成
const newTodo = {
id: Date.now(),
text: formData.text.trim(),
completed: false,
priority: formData.priority
};
// 親コンポーネントに新しいTODOを渡す
onAddTodo(newTodo);
// フォームをリセット
setFormData({
text: '',
priority: 'medium'
});
setError('');
};
return (
<form onSubmit={handleSubmit} className="todo-form">
<div className="form-group">
<input
type="text"
name="text"
value={formData.text}
onChange={handleChange}
placeholder="新しいTODOを入力してください"
className="todo-input"
/>
<select
name="priority"
value={formData.priority}
onChange={handleChange}
className="priority-select"
>
<option value="low">低優先度</option>
<option value="medium">中優先度</option>
<option value="high">高優先度</option>
</select>
<button type="submit" className="add-button">
追加
</button>
</div>
{error && <p className="error-message">{error}</p>}
</form>
);
}
export default TodoForm;
2. App.jsの修正
既存のsrc/App.js
を以下のように修正してください。
import React, { useState } from 'react';
import './App.css';
import TodoHeader from './TodoHeader';
import TodoList from './TodoList';
import TodoForm from './TodoForm';
function App() {
const [todos, setTodos] = useState([
{ id: 1, text: "Reactを学ぶ", completed: false, priority: "high" },
{ id: 2, text: "TODOアプリを作る", completed: true, priority: "medium" },
{ id: 3, text: "データベース連携", completed: false, priority: "low" },
{ id: 4, text: "デプロイする", completed: false, priority: "medium" }
]);
const completedCount = todos.filter(todo => todo.completed).length;
// TODOの完了状態を切り替える関数
const toggleTodo = (id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
};
// 新しいTODOを追加する関数
const addTodo = (newTodo) => {
setTodos(prevTodos => [...prevTodos, newTodo]);
};
return (
<div className="App">
<TodoHeader
title="React TODOアプリ"
totalCount={todos.length}
completedCount={completedCount}
/>
<TodoForm onAddTodo={addTodo} />
<TodoList todos={todos} onToggle={toggleTodo} />
</div>
);
}
export default App;
3. スタイルの追加
既存のsrc/App.css
に以下のスタイルを追加してください。
.todo-form {
margin-bottom: 30px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
}
.form-group {
display: flex;
gap: 10px;
align-items: center;
}
.todo-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.todo-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.priority-select {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
background-color: white;
}
.add-button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.2s;
}
.add-button:hover {
background-color: #0056b3;
}
.add-button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.error-message {
color: #dc3545;
margin-top: 10px;
font-size: 14px;
}
動作確認
実装が完了したら、開発サーバーが起動している状態でブラウザを確認してください。以下の機能が動作することを確認できます。






- 新しいTODOを入力して追加できる
- 優先度を選択できる
- 入力値のバリデーションが動作する
- エラーメッセージが表示される
- 追加後にフォームがクリアされる
FAQ
まとめ
今回は、Reactでのイベントハンドリングとフォーム処理について学習しました。
制御されたコンポーネントを使うことで、フォームの値を完全に制御し、ユーザーの入力に応じて動的にUIを更新することができるようになりました。
バリデーション機能を実装することで、より安全で使いやすいアプリケーションを作ることができます。



次回は、リスト表示と条件付きレンダリングについて学習していきます。TODOリストの表示方法を改善し、フィルタリング機能を追加していきましょう!
この記事が少しでもお役に立ったなら何よりです。次回もお楽しみに!
コメント