Reactで作るTODOアプリ開発シリーズ第3回
この記事では
Reactでの状態管理の基礎、useStateフックの使い方
を学習していきます。
前回は、コンポーネントとJSXについて詳しく学習しました。今回は、Reactアプリケーションを動的にするために欠かせない状態管理について学んでいきましょう。
useStateは、Reactの最も基本的なフックの一つです。これを使うことで、コンポーネント内でデータを保持し、そのデータが変更されたときに自動的にUIを更新することができます。
- useStateフックの基本概念
- 状態の更新方法と注意点
- 配列とオブジェクトの状態管理
- TODOリストの基本構造作成
- TODOアイテムの追加機能実装

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


TODOアプリ開発シリーズのまとめ記事はこちら
前回までのコード
第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の仕組み
- 初期値の設定:
useState(0)
で初期値を0に設定 - 状態とセッター関数:
[count, setCount]
で分割代入
count
: 現在の状態値setCount
: 状態を更新する関数
- 状態の更新:
setCount(count + 1)
で状態を更新 - 自動再レンダリング: 状態が更新されると自動的にコンポーネントが再描画
状態の更新方法と注意点
基本的な状態更新
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の完了状態が切り替わる
- 完了したTODOには取り消し線が表示される
- 統計情報(完了数、未完了数)がリアルタイムで更新される
FAQ
まとめ
今回は、Reactでの状態管理の基礎について学習しました。
useStateフックを使うことで、コンポーネント内でデータを保持し、そのデータが変更されたときに自動的にUIを更新することができるようになりました。
配列やオブジェクトの状態管理では、元のデータを変更せずに新しいデータを作成するという重要な原則を学びました。



次回は、イベントハンドリングとフォーム処理について学習していきます。ユーザーの入力を受け取り、TODOアプリに新しい機能を追加していきましょう!
この記事が少しでもお役に立ったなら何よりです。次回もお楽しみに!
コメント