Udemyセール!最大95%オフ!1,200円~Udemy公式サイト >

この記事にはプロモーションが含まれています。

【React】イベントハンドリングとフォーム処理|TODOアプリ【基本編:第4回】

【React】イベントハンドリングとフォーム処理|TODOアプリ【基本編:第4回】

Reactで作るTODOアプリ開発シリーズ第4回

この記事では

Reactでのイベントハンドリングとフォーム処理の実装方法

を学習していきます。

前回useStateを使った状態管理について学習しました。今回は、ユーザーの操作を受け取り、アプリケーションと対話できるようにするためのイベントハンドリングとフォーム処理について学んでいきましょう。

ユーザーがボタンをクリックしたり、フォームに入力したりしたときの処理を適切に実装することで、より使いやすいアプリケーションを作ることができます。

この記事でわかること
  • イベントハンドラーの書き方と基本概念
  • フォームの制御(制御されたコンポーネント)
  • 入力値の検証とバリデーション
  • TODO作成フォームの実装
  • エラーハンドリングの基礎
ケケンタ

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

TODOアプリ(第4回:完成イメージ)その①
TODOアプリ(第4回:完成イメージ)その①
TODOアプリ(第4回:完成イメージ)その②
TODOアプリ(第4回:完成イメージ)その②
TODOアプリ(第4回:完成イメージ)その③
TODOアプリ(第4回:完成イメージ)その③

TODOアプリ開発シリーズのまとめ記事はこちら



ケケンタ

ケケンタのITブログでは、WebアプリPHPLaravel)やWeb制作WordPressコーディング)について情報を発信しています。
学習中の方や実務をされている方など多くの方にアクセスいただいていますので、ぜひほかの記事も参考にしてみてください!


運動不足、気になっていませんか?

もしプログラミング学習やお仕事で運動不足が気になっているなら

連続屈伸運動がおすすめです!

ボタンにカーソルを合わせるだけ
カウントダウンが始まるタイマーをご用意してみました!

ケケンタ

無理のない範囲で、ぜひ隙間時間に屈伸運動を取り入れてみて下さい!

タイマースタート

3:00

※運動不足だと連続3分で取り組んでもかなり息が切れます
(僕は加えて気分もちょっと悪くなりました……)
絶対にご無理の無い範囲でお取り組みください!



目次

前回までのコード

第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アプリ(第4回:完成イメージ)その①
TODOアプリ(第4回:完成イメージ)その①
TODOアプリ(第4回:完成イメージ)その②
TODOアプリ(第4回:完成イメージ)その②
TODOアプリ(第4回:完成イメージ)その③
TODOアプリ(第4回:完成イメージ)その③
  • 新しいTODOを入力して追加できる
  • 優先度を選択できる
  • 入力値のバリデーションが動作する
  • エラーメッセージが表示される
  • 追加後にフォームがクリアされる

FAQ

イベントハンドラーでエラーが発生する場合

以下の点を確認してください。

  1. イベントハンドラー関数が正しく定義されているか
  2. 関数名が正しく記述されているか(onClick、onChangeなど)
  3. 関数が正しく呼び出されているか
フォームの値が更新されない場合

以下の点を確認してください。

  1. value属性が正しく設定されているか
  2. onChangeイベントハンドラーが正しく動作しているか
  3. 状態更新関数が正しく呼び出されているか
フォーム送信でエラーが発生する場合

以下の点を確認してください。

  1. onSubmitイベントハンドラーが正しく設定されているか
  2. e.preventDefault()が呼び出されているか
  3. フォームのバリデーションが正しく動作しているか
バリデーションが動作しない場合

以下の点を確認してください。

  1. バリデーション条件が正しく記述されているか
  2. エラー状態が正しく更新されているか
  3. エラーメッセージが正しく表示されているか
フォームがクリアされない場合

以下の点を確認してください。

  1. フォーム送信後に状態が正しくリセットされているか
  2. 入力フィールドのvalue属性が正しく更新されているか
  3. フォームの初期状態が正しく設定されているか
キーボードイベントが動作しない場合

以下の点を確認してください。

  1. onKeyDownイベントハンドラーが正しく設定されているか
  2. キーコードの判定が正しく記述されているか
  3. イベントの伝播が正しく制御されているか
フォームのスタイルが適用されない場合

以下の点を確認してください。

  1. CSSクラス名が正しく記述されているか
  2. CSSファイルが正しくimportされているか
  3. スタイルの優先度が適切か

まとめ

今回は、Reactでのイベントハンドリングとフォーム処理について学習しました。

制御されたコンポーネントを使うことで、フォームの値を完全に制御し、ユーザーの入力に応じて動的にUIを更新することができるようになりました。

バリデーション機能を実装することで、より安全で使いやすいアプリケーションを作ることができます。

ケケンタ

次回は、リスト表示と条件付きレンダリングについて学習していきます。TODOリストの表示方法を改善し、フィルタリング機能を追加していきましょう!

この記事が少しでもお役に立ったなら何よりです。次回もお楽しみに!


僕が実際にReact学習に使用した書籍です

【React】イベントハンドリングとフォーム処理|TODOアプリ【基本編:第4回】のアイキャッチ画像

この記事が気に入ったら
フォローしてね!

この記事が良いと思ったらシェアしてね!

コメント

コメントする

CAPTCHA


目次