6-2 TODOアプリの実装
モックサーバーと通信してTODOを取得する
- json-serverの起動
db.json
{
"todos": [
{
"id": 1,
"content": "Create react appをインストールする",
"done": true
},
{
"id": 2,
"content": "JSON Server仮のAPIを作成する",
"done": false
},
{
"id": 3,
"content": "Chakra UIをインストールする",
"done": false
}
]
}
json-server --watch db.json --port 3100
TODOアプリの作成(リスト取得のみ)
src\components\App.js
import React, { useState, useEffect } from "react";
import axios from "axios";
const todoDataUrl = "http://localhost:3100/todos";
function App() {
const [todoList, setTodoList] = useState([]);
// useEffectで、コンポーネントのマウント後に処理を実行
useEffect(() => {
// 非同期処理
const fetchData = async () => {
const response = await axios.get(todoDataUrl);
setTodoList(response.data);
};
fetchData();
}, []);
console.log("TODOリスト:", todoList);
return (
<>
<h1>TODO進捗管理</h1>
<textarea />
<button>+ TODOを追加</button>
<h2>TODOリスト</h2>
<ul>
{todoList.map((todo) => (
<li key={todo.id}>
{todo.content}({todo.done ? "完了" : "未完了"})
</li>
))}
</ul>
</>
);
}
export default App;
TODO一覧を状態別に表示
- todoListをdoneのture/falseによって、incompletedListとcompletedListに分ける
src\components\App.js
import React, { useState, useEffect } from "react";
import axios from "axios";
const todoDataUrl = "http://localhost:3100/todos";
function App() {
const [todoList, setTodoList] = useState([]);
// useEffectで、コンポーネントのマウント後に処理を実行
useEffect(() => {
// 非同期処理
const fetchData = async () => {
const response = await axios.get(todoDataUrl);
setTodoList(response.data);
};
fetchData();
}, []);
console.log("TODOリスト:", todoList);
// 未完了リスト
const incompletedList = todoList.filter((todo) => !todo.done);
// 完了リスト
const completedList = todoList.filter((todo) => todo.done);
return (
<>
<h1>TODO進捗管理</h1>
<textarea />
<button>+ TODOを追加</button>
<h2>未完了TODOリスト</h2>
<ul>
{incompletedList.map((todo) => (
<li key={todo.id}>
{todo.content}
<button>未完了リストへ</button>
<button>削除</button>
</li>
))}
</ul>
<h2>完了TODOリスト</h2>
<ul>
{completedList.map((todo) => (
<li key={todo.id}>
{todo.content}
<button>未完了リストへ</button>
<button>削除</button>
</li>
))}
</ul>
</>
);
}
export default App;
タイトルとTODOリストをコンポーネント化する
- TodoTitle
- h1やh2の大きな文字を表示する
- TodoItem
- 1つのTodo、内容と移動・削除ボタン
- TodoList
- TodoItemをループして表示
src\components\App.js
import React, { useState, useEffect } from "react";
import axios from "axios";
const todoDataUrl = "http://localhost:3100/todos";
// タイトルの表示コンポーネント
const TodoTitle = ({ title, as }) => {
if (as === "h1") {
return <h1>{title}</h1>;
} else if (as === "h2") {
return <h2>{title}</h2>;
} else {
return <p>{title}</p>;
}
};
// 1つのTodo、内容と移動・削除ボタン
const TodoItem = ({ todo }) => {
return (
<>
{todo.content}
<button>{todo.done ? "未完了リストへ" : "完了リストへ"}</button>
<button>削除</button>
</>
);
};
// TodoItemをループして表示
const TodoList = ({ todoList }) => {
return (
<ul>
{todoList.map((todo) => (
<li key={todo.id}>
<TodoItem todo={todo} />
</li>
))}
</ul>
);
};
function App() {
const [todoList, setTodoList] = useState([]);
// useEffectで、コンポーネントのマウント後に処理を実行
useEffect(() => {
// 非同期処理
const fetchData = async () => {
const response = await axios.get(todoDataUrl);
setTodoList(response.data);
};
fetchData();
}, []);
console.log("TODOリスト:", todoList);
// 未完了リスト
const incompletedList = todoList.filter((todo) => !todo.done);
// 完了リスト
const completedList = todoList.filter((todo) => todo.done);
return (
<>
<TodoTitle title="TODO進捗管理" as="h1" />
<textarea />
<button>+ TODOを追加</button>
<TodoTitle title="未完了TODOリスト" as="h2" />
<TodoList todoList={incompletedList} />
<TodoTitle title="完了TODOリスト" as="h2" />
<TodoList todoList={completedList} />
</>
);
}
export default App;
サーバと通信するtodo.jsの作成
src\apis\todos.js
import axios from "axios";
const todoDataUrl = "http://localhost:3100/todos";
// 全TODOリスト取得
export const getAllTodosData = async () => {
const response = await axios.get(todoDataUrl);
return response.data;
};
// 1件のTODOを追加する
export const addTodoData = async (todo) => {
const response = await axios.post(todoDataUrl, todo);
return response.data;
};
// 1件のTODOを削除する
export const deleteTodoData = async (id) => {
await axios.delete(`${todoDataUrl}/${id}`);
return id;
};
// 1件のTODOを更新する
export const updateTodoData = async (id, todo) => {
const response = await axios.put(`${todoDataUrl}/${id}`, todo);
return response.data;
};
TODOを取得、追加、更新、削除するカスタムフックを作成する
src\hooks\useTodo.js
import React, { useState, useEffect } from "react";
import { ulid } from "ulid";
import * as todoData from "../apis/todos";
export const useTodo = () => {
const [todoList, setTodoList] = useState([]);
useEffect(() => {
todoData.getAllTodosData().then((todo) => {
console.log(todo);
setTodoList([...todo].reverse());
});
}, []);
// todoのdoneを反転させる
const toggleTodoListItemStatus = (id, done) => {
// todoListから、idが一致する1件を取り出す
const todoItem = todoList.find((item) => item.id === id);
// doneを反転させて、新たなitemを作成
const newTodoItem = { ...todoItem, done: !done };
// サーバに更新API呼ぶ
todoData.updateTodoData(id, newTodoItem).then((updatedTodo) => {
// 成功したら、todoListを更新。idが一致しているものを、サーバーから返ってきたupdatedTodoで更新する
const newTodoList = todoList.map((item) => (item.id !== updatedTodo.id ? item : updatedTodo));
// 新しいtodoListをstateにセットする
setTodoList(newTodoList);
});
};
const addTodoListItem = (todoContent) => {
// あたらしいitemを作成する
const newTodoItem = { id: ulid, content: todoContent, done: false };
// サーバーの追加APIを呼ぶ
todoData.addTodoData(newTodoItem).then((addTodo) => {
// addTodoをtodoListに追加してstateにセットする
setTodoList([addTodo, ...todoList]);
});
};
const deleteTodoListItem = (id) => {
// サーバーの削除APIを呼ぶ
todoData.deleteTodoData(id).then((deletedid) => {
const newTodoList = todoList.filter((item) => item.id !== deletedid);
// 1件削除された新しいtodoListに追加してstateにセットする
setTodoList(newTodoList);
});
};
// 作成した関数を返す
return { todoList, toggleTodoListItemStatus, addTodoListItem, deleteTodoListItem };
};
完成したTODOリスト
src/
┣ apis/
┃ ┗ todos.js
┣ components/
┃ ┣ App.js
┃ ┣ TodoAdd.js
┃ ┣ TodoItem.js
┃ ┣ TodoList.js
┃ ┗ TodoTitle.js
┣ hooks/
┃ ┗ useTodo.js
┗ index.js
src\apis\todos.js
import axios from "axios";
const todoDataUrl = "http://localhost:3100/todos";
// 全TODOリスト取得
export const getAllTodosData = async () => {
const response = await axios.get(todoDataUrl);
return response.data;
};
// 1件のTODOを追加する
export const addTodoData = async (todo) => {
const response = await axios.post(todoDataUrl, todo);
return response.data;
};
// 1件のTODOを削除する
export const deleteTodoData = async (id) => {
await axios.delete(`${todoDataUrl}/${id}`);
return id;
};
// 1件のTODOを更新する
export const updateTodoData = async (id, todo) => {
const response = await axios.put(`${todoDataUrl}/${id}`, todo);
return response.data;
};
src\components\App.js
import React, { useRef } from "react";
import { useTodo } from "../hooks/useTodo";
import { TodoAdd } from "./TodoAdd";
import { TodoList } from "./TodoList";
import { TodoTitle } from "./TodoTitle";
function App() {
const { todoList, toggleTodoListItemStatus, addTodoListItem, deleteTodoListItem } = useTodo();
const inputEl = useRef(null);
const handleAddTodoListItem = () => {
if (inputEl.current.value === "") {
return;
}
addTodoListItem(inputEl.current.value);
inputEl.current.value = "";
};
console.log("TODOリスト:", todoList);
// 未完了リスト
const incompletedList = todoList.filter((todo) => !todo.done);
// 完了リスト
const completedList = todoList.filter((todo) => todo.done);
return (
<>
<TodoTitle title="TODO進捗管理" as="h1" />
<TodoAdd buttonText="+ TODOを追加" inputEl={inputEl} handleAddTodoListItem={handleAddTodoListItem} />
<TodoList todoList={incompletedList} toggleTodoListItemStatus={toggleTodoListItemStatus} deleteTodoListItem={deleteTodoListItem} title="未完了TODOリスト" as="h2" />
<TodoList todoList={completedList} toggleTodoListItemStatus={toggleTodoListItemStatus} deleteTodoListItem={deleteTodoListItem} title="完了TODOリスト" as="h2" />
</>
);
}
export default App;
src\components\TodoAdd.js
export const TodoAdd = ({ buttonText, inputEl, handleAddTodoListItem }) => {
return (
<>
<textarea ref={inputEl} />
<button onClick={handleAddTodoListItem}>{buttonText}</button>
</>
);
};
src\components\TodoItem.js
// 1つのTodo、内容と移動・削除ボタン
export const TodoItem = ({ todo, toggleTodoListItemStatus, deleteTodoListItem }) => {
// onClickイベントが発生したら、useTodoフックを呼び出す
const handleToggleTodoListItemStatus = () => toggleTodoListItemStatus(todo.id, todo.done);
const handleDeleteTodoListItem = () => deleteTodoListItem(todo.id);
return (
<>
{todo.content}
<button onClick={handleToggleTodoListItemStatus}>{todo.done ? "未完了リストへ" : "完了リストへ"}</button>
<button onClick={handleDeleteTodoListItem}>削除</button>
</>
);
};
src\components\TodoList.js
import { TodoTitle } from "./TodoTitle";
import { TodoItem } from "./TodoItem";
// TodoItemをループして表示
// todoListが0件の場合、タイトルとTODOリストを表示しない
export const TodoList = ({ todoList, toggleTodoListItemStatus, deleteTodoListItem, title, as }) => {
return (
<>
{todoList.length !== 0 && (
<>
<TodoTitle title={title} as={as} />
<ul>
{todoList.map((todo) => (
<li key={todo.id}>
<TodoItem todo={todo} key={todo.id} toggleTodoListItemStatus={toggleTodoListItemStatus} deleteTodoListItem={deleteTodoListItem} />
</li>
))}
</ul>
</>
)}
</>
);
};
src\components\TodoTitle.js
import React, { memo } from "react";
// タイトルの表示コンポーネント
export const TodoTitle = memo(({ title, as }) => {
if (as === "h1") {
return <h1>{title}</h1>;
} else if (as === "h2") {
return <h2>{title}</h2>;
} else {
return <p>{title}</p>;
}
});
src\hooks\useTodo.js
import React, { useState, useEffect } from "react";
import { ulid } from "ulid";
import * as todoData from "../apis/todos";
export const useTodo = () => {
const [todoList, setTodoList] = useState([]);
useEffect(() => {
todoData.getAllTodosData().then((todo) => {
console.log(todo);
setTodoList([...todo].reverse());
});
}, []);
// todoのdoneを反転させる
const toggleTodoListItemStatus = (id, done) => {
// todoListから、idが一致する1件を取り出す
const todoItem = todoList.find((item) => item.id === id);
// doneを反転させて、新たなitemを作成
const newTodoItem = { ...todoItem, done: !done };
// サーバに更新API呼ぶ
todoData.updateTodoData(id, newTodoItem).then((updatedTodo) => {
// 成功したら、todoListを更新。idが一致しているものを、サーバーから返ってきたupdatedTodoで更新する
const newTodoList = todoList.map((item) => (item.id !== updatedTodo.id ? item : updatedTodo));
// 新しいtodoListをstateにセットする
setTodoList(newTodoList);
});
};
const addTodoListItem = (todoContent) => {
// あたらしいitemを作成する
const newTodoItem = { id: ulid, content: todoContent, done: false };
// サーバーの追加APIを呼ぶ
todoData.addTodoData(newTodoItem).then((addTodo) => {
// addTodoをtodoListに追加してstateにセットする
setTodoList([addTodo, ...todoList]);
});
};
const deleteTodoListItem = (id) => {
// サーバーの削除APIを呼ぶ
todoData.deleteTodoData(id).then((deletedid) => {
const newTodoList = todoList.filter((item) => item.id !== deletedid);
// 1件削除された新しいtodoListに追加してstateにセットする
setTodoList(newTodoList);
});
};
// 作成した関数を返す
return { todoList, toggleTodoListItemStatus, addTodoListItem, deleteTodoListItem };
};