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

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

【React】状態管理の基礎 – useState|TODOアプリ【基本編:第3回】

【React】状態管理の基礎 – useState|TODOアプリ【基本編:第3回】

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

この記事では

Reactでの状態管理の基礎、useStateフックの使い方

を学習していきます。

前回は、コンポーネントとJSXについて詳しく学習しました。今回は、Reactアプリケーションを動的にするために欠かせない状態管理について学んでいきましょう。

useStateは、Reactの最も基本的なフックの一つです。これを使うことで、コンポーネント内でデータを保持し、そのデータが変更されたときに自動的にUIを更新することができます。

この記事でわかること
  • useStateフックの基本概念
  • 状態の更新方法と注意点
  • 配列とオブジェクトの状態管理
  • TODOリストの基本構造作成
  • TODOアイテムの追加機能実装
ケケンタ

状態管理を理解することで、ユーザーの操作に応じて動的に変化するアプリケーションを作ることができるようになります!

TODOアプリ(第3回:完成イメージ)
TODOアプリ(第3回:完成イメージ)

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



ケケンタ

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


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

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

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

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

ケケンタ

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

タイマースタート

3:00

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



目次

前回までのコード

第2回で作成したコードを確認しましょう。以下のファイル構成になっています:

ファイル構造

todo-app/
├── src/
│   ├── App.js
│   ├── App.css
│   ├── TodoItem.js
│   ├── TodoList.js
│   └── TodoHeader.js

App.js

import React from 'react';
import './App.css';
import TodoHeader from './TodoHeader';
import TodoList from './TodoList';

function App() {
  const todos = [
    { text: "Reactを学ぶ", completed: false, priority: "high" },
    { text: "TODOアプリを作る", completed: true, priority: "medium" },
    { text: "データベース連携", completed: false, priority: "low" },
    { text: "デプロイする", completed: false, priority: "medium" }
  ];

  const completedCount = todos.filter(todo => todo.completed).length;

  return (
    <div className="App">
      <TodoHeader 
        title="React TODOアプリ"
        totalCount={todos.length}
        completedCount={completedCount}
      />
      <TodoList todos={todos} />
    </div>
  );
}

export default App;

TodoList.js

import React from 'react';
import TodoItem from './TodoItem';

function TodoList({ todos }) {
  return (
    <div className="todo-list">
      {todos.map((todo, index) => (
        <TodoItem 
          key={index}
          text={todo.text}
          completed={todo.completed}
          priority={todo.priority}
        />
      ))}
    </div>
  );
}

export default TodoList;

TodoHeader.js

import React from 'react';

function TodoHeader({ title, totalCount, completedCount }) {
  return (
    <div className="todo-header">
      <h1>{title}</h1>
      <div className="todo-stats">
        <span>総数: {totalCount}</span>
        <span>完了: {completedCount}</span>
        <span>未完了: {totalCount - completedCount}</span>
      </div>
    </div>
  );
}

export default TodoHeader;

TodoItem.js

import React from 'react';

function TodoItem({ text, completed, priority = "medium" }) {
  return (
    <div className={`todo-item priority-${priority}`}>
      <input 
        type="checkbox" 
        checked={completed} 
        readOnly 
      />
      <span style={{ 
        textDecoration: completed ? 'line-through' : 'none' 
      }}>
        {text}
      </span>
    </div>
  );
}

export default TodoItem;

App.css

.todo-header {
  margin-bottom: 30px;
}

.todo-stats {
  display: flex;
  justify-content: center;
  gap: 20px;
  margin-top: 10px;
  font-size: 14px;
  color: #666;
}

.todo-stats span {
  padding: 5px 10px;
  background-color: #f5f5f5;
  border-radius: 15px;
}

.priority-high {
  border-left: 4px solid #ff4757;
}

.priority-medium {
  border-left: 4px solid #ffa502;
}

.priority-low {
  border-left: 4px solid #2ed573;
}

useStateフックの基本概念

実際に手を動かす前に、まずは必要な知識について解説をしていきます。

useStateは、Reactコンポーネントで状態(state)を管理するためのフックです。状態とは、コンポーネントが保持するデータで、このデータが変更されると自動的にコンポーネントが再レンダリングされます。

useStateの基本構文

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        増やす
      </button>
    </div>
  );
}

useStateの仕組み

  1. 初期値の設定: useState(0)で初期値を0に設定
  2. 状態とセッター関数: [count, setCount]で分割代入
  • count: 現在の状態値
  • setCount: 状態を更新する関数
  1. 状態の更新: setCount(count + 1)で状態を更新
  2. 自動再レンダリング: 状態が更新されると自動的にコンポーネントが再描画

状態の更新方法と注意点

基本的な状態更新

function Example() {
  const [name, setName] = useState("React");

  const handleClick = () => {
    setName("JavaScript"); // 直接値を設定
  };

  return (
    <div>
      <p>名前: {name}</p>
      <button onClick={handleClick}>名前を変更</button>
    </div>
  );
}

関数を使った状態更新

前の状態をもとに新しい状態を計算する場合は、関数を使うことができます。

function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(prevCount => prevCount + 1); // 前の状態を基に更新
  };

  const decrement = () => {
    setCount(prevCount => prevCount - 1);
  };

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </div>
  );
}

重要な注意点

状態の更新は非同期で行われるため、以下のような書き方は避けるべきです。

// ❌ 避けるべき書き方
function BadExample() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
    setCount(count + 1); // 前の更新が反映される前に実行される
  };
}

// ✅ 正しい書き方
function GoodExample() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1); // 前の状態を基に更新
  };
}

配列とオブジェクトの状態管理

配列の状態管理

配列の状態を更新する際は、元の配列を変更せずに新しい配列を作成する必要があります。

function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: "Reactを学ぶ", completed: false },
    { id: 2, text: "TODOアプリを作る", completed: true }
  ]);

  // 新しいTODOを追加
  const addTodo = (text) => {
    const newTodo = {
      id: Date.now(), // 簡単なID生成
      text: text,
      completed: false
    };
    setTodos(prevTodos => [...prevTodos, newTodo]); // スプレッド構文で新しい配列を作成
  };

  // TODOを削除
  const deleteTodo = (id) => {
    setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
  };

  // TODOの完了状態を切り替え
  const toggleTodo = (id) => {
    setTodos(prevTodos => 
      prevTodos.map(todo => 
        todo.id === id 
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    );
  };

  return (
    <div>
      {todos.map(todo => (
        <div key={todo.id}>
          <input 
            type="checkbox" 
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
          />
          <span style={{ 
            textDecoration: todo.completed ? 'line-through' : 'none' 
          }}>
            {todo.text}
          </span>
          <button onClick={() => deleteTodo(todo.id)}>削除</button>
        </div>
      ))}
    </div>
  );
}

オブジェクトの状態管理

オブジェクトの状態を更新する際も、元のオブジェクトを変更せずに新しいオブジェクトを作成します。

function UserProfile() {
  const [user, setUser] = useState({
    name: "田中太郎",
    age: 25,
    email: "tanaka@example.com"
  });

  // 名前を更新
  const updateName = (newName) => {
    setUser(prevUser => ({
      ...prevUser, // 既存のプロパティをコピー
      name: newName // 名前のみ更新
    }));
  };

  // 年齢を更新
  const updateAge = (newAge) => {
    setUser(prevUser => ({
      ...prevUser,
      age: newAge
    }));
  };

  return (
    <div>
      <p>名前: {user.name}</p>
      <p>年齢: {user.age}</p>
      <p>メール: {user.email}</p>
      <button onClick={() => updateName("佐藤花子")}>
        名前を変更
      </button>
      <button onClick={() => updateAge(30)}>
        年齢を変更
      </button>
    </div>
  );
}

【実践】useStateを使った動的なTODOアプリ作成

ここからは実際に手を動かして、useStateを使った動的なTODOアプリケーションを作成していきましょう。

1. App.jsの修正

既存のsrc/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;

2. TodoList.jsの修正

既存のsrc/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;

3. TodoItem.jsの修正

既存のsrc/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;

動作確認

実装が完了したら、開発サーバーが起動している状態でブラウザを確認してください。以下の機能が動作することを確認できます。

TODOアプリ(第3回:完成イメージ)
TODOアプリ(第3回:完成イメージ)
  • チェックボックスをクリックすると、TODOの完了状態が切り替わる
  • 完了したTODOには取り消し線が表示される
  • 統計情報(完了数、未完了数)がリアルタイムで更新される

FAQ

useStateでエラーが発生する場合

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

  1. import { useState } from 'react'が正しく記述されているか
  2. useStateの構文が正しいか:const [state, setState] = useState(initialValue)
  3. コンポーネントの最上位で呼び出しているか
状態が更新されない場合

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

  1. setState関数を正しく呼び出しているか
  2. 状態更新関数内で元の状態を変更していないか(イミュータブルな更新を心がける)
  3. 依存関係が正しく設定されているか
配列の状態更新でエラーが発生する場合

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

  1. 元の配列を直接変更していないか(push、pop、spliceなどは避ける)
  2. 新しい配列を作成しているか(map、filter、spread演算子を使用)
  3. keyプロパティが正しく設定されているか
オブジェクトの状態更新でエラーが発生する場合

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

  1. 元のオブジェクトを直接変更していないか
  2. spread演算子(...)を使用して新しいオブジェクトを作成しているか
  3. プロパティ名が正しく記述されているか
無限ループが発生する場合

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

  1. 状態更新関数が条件なしで呼び出されていないか
  2. useEffect内で依存配列が正しく設定されているか
  3. 状態更新が無限に繰り返されていないか
パフォーマンスの問題が発生する場合

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

  1. 不要な再レンダリングが発生していないか
  2. 大きな配列やオブジェクトを直接状態として使用していないか
  3. 状態更新が頻繁に発生していないか
状態の初期値が正しく設定されない場合

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

  1. useStateの引数に正しい初期値を渡しているか
  2. 初期値の型が期待する型と一致しているか
  3. 初期値の計算が重い処理になっていないか

まとめ

今回は、Reactでの状態管理の基礎について学習しました。

useStateフックを使うことで、コンポーネント内でデータを保持し、そのデータが変更されたときに自動的にUIを更新することができるようになりました。

配列やオブジェクトの状態管理では、元のデータを変更せずに新しいデータを作成するという重要な原則を学びました。

ケケンタ

次回は、イベントハンドリングとフォーム処理について学習していきます。ユーザーの入力を受け取り、TODOアプリに新しい機能を追加していきましょう!

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


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

【React】状態管理の基礎 - useState|TODOアプリ【基本編:第3回】のアイキャッチ画像

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

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

コメント

コメントする

CAPTCHA


目次