React と MUI で ToDo アプリを作ろう!
🎯 はじめに
本記事では、React と Material UI(MUI) を使って、シンプルな ToDo リストアプリを一から構築するハンズオンを紹介します。まずは基本機能を実装し、その後でMUIを使ってデザインを整えていきます。
- ✅ Reactの基本的な構成が学べる
- ✅ 機能実装からスタイリングまでステップバイステップで解説
- ✅ MUIによるスタイリングの実践
完成イメージはこちら(GitHub リポジトリ)👇
👉 https://github.com/Ash65536/todo-app/
🛠 開発環境のセットアップ
まずは React アプリを作成します。
npx create-react-app todo-app
cd todo-app
📁 プロジェクト構成
最終的に以下のような構成になります。まずは機能を実装し、後からMUIを導入します。
src/
├── App.js # アプリのメインコンポーネント
├── TodoForm.js # タスク入力フォーム
├── TodoList.js # タスク一覧表示
🧩 Step 1: 基本機能の実装
App.js の作成
アプリの中心となるコンポーネントを作成します。
import React, { useState } from 'react';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
function App() {
const [todos, setTodos] = useState([]);
// 新しいToDoを追加する関数
const addTodo = (text) => {
const newTodo = { id: Date.now(), text, completed: false };
setTodos([...todos, newTodo]);
};
// ToDoの完了状態を切り替える関数
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
// ToDoを削除する関数
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div className="app">
<h1>ToDoリスト</h1>
<TodoForm addTodo={addTodo} />
<TodoList
todos={todos}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
/>
</div>
);
}
export default App;
TodoForm.js の作成
タスク入力用フォームコンポーネントを作成します。
import { useState } from "react";
const TodoForm = ({ addTodo }) => {
const [text, setText] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim() === "") return;
addTodo(text);
setText("");
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="新しいタスクを入力"
/>
<button type="submit">追加</button>
</form>
);
};
export default TodoForm;
TodoList.js の作成
タスク一覧を表示するコンポーネントを作成します。
const TodoList = ({ todos, toggleTodo, deleteTodo }) => {
if (todos.length === 0) {
return <p>タスクがありません。新しいタスクを追加してください。</p>;
}
return (
<ul className="todo-list">
{todos.map((todo) => (
<li 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>
</li>
))}
</ul>
);
};
export default TodoList;
▶️ 基本機能の確認
この時点で開発サーバーを起動して、機能を確認してみましょう。
npm start
ブラウザで http://localhost:3000 にアクセスすると、基本機能を持ったToDoアプリが表示されます。
🎨 Step 2: MUIの導入とスタイリング
次にMaterial UIをインストールして、アプリにスタイルを適用していきます。
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
App.js の修正
MUIのコンポーネントを使ってスタイリングします。
import React, { useState } from 'react';
import { Container, Typography, Box } from '@mui/material';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
function App() {
const [todos, setTodos] = useState([]);
// 新しいToDoを追加する関数
const addTodo = (text) => {
const newTodo = { id: Date.now(), text, completed: false };
setTodos([...todos, newTodo]);
};
// ToDoの完了状態を切り替える関数
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
// ToDoを削除する関数
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<Container maxWidth="sm" sx={{ mt: 4 }}>
<Box sx={{ textAlign: 'center', mb: 4 }}>
<Typography variant="h4" component="h1" gutterBottom>
ToDoリスト
</Typography>
</Box>
<TodoForm addTodo={addTodo} />
<TodoList
todos={todos}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
/>
</Container>
);
}
export default App;
TodoForm.js の修正
フォームをMUIコンポーネントで修正します。
import { useState } from "react";
import { Box, Button, TextField } from "@mui/material";
const TodoForm = ({ addTodo }) => {
const [text, setText] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim() === "") return;
addTodo(text);
setText("");
};
return (
<Box component="form" onSubmit={handleSubmit} sx={{ display: "flex", mb: 3 }}>
<TextField
label="新しいタスクを入力"
variant="outlined"
value={text}
onChange={(e) => setText(e.target.value)}
fullWidth
sx={{ mr: 2 }}
/>
<Button
type="submit"
variant="contained"
color="primary"
disabled={text.trim() === ""}
>
追加
</Button>
</Box>
);
};
export default TodoForm;
TodoList.js の修正
タスク一覧をMUIでスタイリングします。
import {
List,
ListItem,
Checkbox,
IconButton,
ListItemText,
ListItemButton,
ListItemIcon,
Paper
} from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
const TodoList = ({ todos, toggleTodo, deleteTodo }) => {
if (todos.length === 0) {
return <Paper sx={{ p: 2, textAlign: 'center' }}>タスクがありません。新しいタスクを追加してください。</Paper>;
}
return (
<Paper elevation={2}>
<List sx={{ width: '100%' }}>
{todos.map((todo) => (
<ListItem
key={todo.id}
secondaryAction={
<IconButton
edge="end"
onClick={() => deleteTodo(todo.id)}
>
<DeleteIcon />
</IconButton>
}
disablePadding
>
<ListItemButton onClick={() => toggleTodo(todo.id)} dense>
<ListItemIcon>
<Checkbox
edge="start"
checked={todo.completed}
tabIndex={-1}
disableRipple
/>
</ListItemIcon>
<ListItemText
primary={todo.text}
sx={{
textDecoration: todo.completed ? "line-through" : "none",
color: todo.completed ? "text.secondary" : "text.primary"
}}
/>
</ListItemButton>
</ListItem>
))}
</List>
</Paper>
);
};
export default TodoList;
⚙️ index.js の修正
MUIのテーマを適用するためにindex.jsを修正します。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
const theme = createTheme({
palette: {
primary: {
main: '#2196f3',
},
secondary: {
main: '#f50057',
},
},
});
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
</React.StrictMode>
);
🚀 Step 3: 機能拡張
基本のToDoアプリが完成したら、さらに機能を拡張してみましょう。例として、ローカルストレージへの保存機能を実装します。
ローカルストレージへの保存機能
App.jsを修正して、タスクをローカルストレージに保存します。
import React, { useState, useEffect } from 'react';
import { Container, Typography, Box } from '@mui/material';
import TodoForm from './TodoForm';
import TodoList from './TodoList';
function App() {
// ローカルストレージからタスクを読み込む
const [todos, setTodos] = useState(() => {
const savedTodos = localStorage.getItem('todos');
return savedTodos ? JSON.parse(savedTodos) : [];
});
// タスクが変更されたらローカルストレージに保存
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
const addTodo = (text) => {
const newTodo = { id: Date.now(), text, completed: false };
setTodos([...todos, newTodo]);
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<Container maxWidth="sm" sx={{ mt: 4 }}>
<Box sx={{ textAlign: 'center', mb: 4 }}>
<Typography variant="h4" component="h1" gutterBottom>
ToDoリスト
</Typography>
</Box>
<TodoForm addTodo={addTodo} />
<TodoList
todos={todos}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
/>
</Container>
);
}
export default App;
▶️ 完成したアプリを実行しよう!
開発サーバーを起動して、完成したToDoアプリを確認しましょう。
npm start
🔍 さらなる機能拡張のアイデア
このアプリをベースに、以下のような機能を追加してみるのもおすすめです:
- タスクの編集機能
- 日付や優先度の追加
- フィルタリング機能(完了/未完了の表示切替)
- タスクのカテゴリ分け
- ドラッグ&ドロップによる並べ替え
- ダークモード対応
🧑💻 GitHub リポジトリ
完全なコードは GitHub に公開しています👇
📎 https://github.com/Ash65536/todo-app
📝 おわりに
本記事では、Reactの基本機能を使ったToDoアプリの実装から、MUIを使ったスタイリングまでステップバイステップで解説しました。React の状態管理や、MUI のコンポーネントの活用方法を学ぶことができたと思います。これをベースに機能を拡張して、オリジナルの ToDo アプリを作成してみてください!
質問やフィードバックがあれば、コメント欄にお願いします。
Discussion