Next.jsでTodoアプリを作ったのでメモ。今更ながらCRUD(クラッド)について再認識しています。
アプリではReactでフォームを簡単に作成できるReact Hook Formというライブラリを使っています。そのなかでもフィールドを動的に増減できるuseFieldArryという機能を使いました。
登録したタスクはAPIで更新されることを前提としているので、json-serverを使います。これはAPIモックサーバーが簡単に使用できるライブラリです。
今回はCRUDについて調べたのでjson-server、React Hook Formについてのメモは特になしです。
作成したサンプル
こちらが作成したToDoリストです。右下の「追加する」をクリックすると、タスクが追加されます。
追加したタスクは編集も可能です。完了をチェックすると完了状態になり、完了したタスクは削除することができます。
コード(GitHub)
こちらがコードになります。
アプリ起動方法
ToDoアプリを起動する前に、APIモックサーバーを起動します。
# APIモックサーバーを起動
$ npm run mock:start
ポート3001で起動するので、変更が必要でしたらpackage.jsonの”json-server –watch mock/db.json -p 3001″のポート番号「3001」を好きな番号に変更します。
{
"name": "workspace",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"mock:start": "json-server --watch mock/db.json -p 3001" ※必要に応じてポート番号変更
},
・・・・
次にアプリを開発環境で立ち上げます。これでアプリの動作確認ができます。
# アプリを起動
$ npm run dev
読み込むjsonデータ
モックデータとして次の様なjsonファイルを用意します。こちらを初期値としてToDoアプリに表示させます。
{
"todo": [
{
"todoItem": "タスク1",
"isTodoFinish": false,
"id": 1
},
{
"todoItem": "タスク2",
"isTodoFinish": false,
"id": 2
},
{
"todoItem": "タスク4",
"isTodoFinish": false,
"id": 3
}
]
}
アプリのディレクトリ構成
ディレクトリ構成は次の様になっています。※ToDoに関するディレクトリを抜粋しました。
要となるのは、ToDoを表示するコンポーネントです。
具体的にはToDoリスト一覧を表示するTodoListと、タスクの編集状態を表示するEditTodoItemRowです。
.
├── app
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components # コンポーネント
│ └── elements
│ ├── EditTodoItemRow.tsx # タスク編集状態
│ └── TodoList.tsx # Todoリスト
├── mock
│ └── db.json
├── package-lock.json
├── package.json
├── postcss.config.js
└── tsconfig.json
タスクが編集状態の時は、タスクをEditTodoItemRowコンポーネントに切り替えます。タスクを追加する場合は、EditTodoItemRowコンポーネントを追加します。
アプリ一覧を表示するコンポーネント(コード全体)
少しコードが長いですが、こちらがToDoListコンポーネント(TodoList.tsx)です。
編集と新規登録のタスクを表示するコンポーネント(EditTodoItemRow.tsx)がこちらです。
状態管理(State)を設定する
状態管理(State)として、ToDo一覧を管理するtodoItemsと、編集するタスクのIDを管理するeditItemIdを準備します。
// ToDo一覧を管理(State)
const [todoItems, setTodoItems] = useState<TodoItem[]>();
// 編集するタスクのIDを管理(State)
const [editItemId, setEditItemId] = useState<TodoItem["id"] | "newItem">();
アプリのCRUD
CRUD(クラッド)はアプリの基本機能の用語で、 Create(作成)、Read(読み取り)、Update(更新)、Delete(削除)の頭文字を並べたものになります。
今回のアプリで、この機能に該当するのはこちらです。
Read(読み取り)
Read(読み取り)は、jsonデータ(ToDo一覧)を取得して画面に表示させます。
初回アクセスの際、useEffect()で取得したjsonデータをtodoItems(タスク一覧を管理するState)に格納します。コードはこの部分です。
// jsonを取得してStateに格納
const fetchTodoItems = async () => {
const { data } = await axios.get(apiURL);
setTodoItems(data);
};
useEffect(() => {
// 初期データの取得
fetchTodoItems();
}, []);
Read(読み取り)は、初回アクセス以外に、Create(作成)、Update(更新)、Delete(削除)でも発生します。ただ、キャンセルで変更のない場合は何もしません。
タスク編集状態の判定
タスクを編集状態にするには、「編集」ボタンをクリックした際、editItemId(State)を自身のタスクIDに変更します。
// 編集をクリックしたらsetEditItemId()を更新する
<button
type="button"
disabled={editItemId !== undefined}
onClick={(e) => {
setEditItemId(item.id);
}}
>
変更
</button>
Stateを更新したらToDo一覧が再描画されます。その際、条件にStateで管理しているIDと一致していたタスクは、EditTodoItemRowコンポーネントを表示して編集状態にします。
{todoItems?.map((item) =>
editItemId === item.id ? (
// タスク編集状態
<EditTodoItemRow />
) : (
// タスク通常
<tr key={item.id}>
<td style={{width: "80px"}}>{item.id}</td>
<td>
・・・・
</td>
</tr>
)
)}
Create(作成)・Update(更新)
Create(作成)
ToDo画面の右下の「追加」をクリックすると入力画面が表示されるので、入力して登録します。
Update(更新)
更新は、該当のタスクの「変更」をクリックすると編集モードになります。編集が完了したら登録をクリックします。
こちらがCreate(作成)とUpdate(更新)の処理になります。
フォームを送信した際、CreateとUpdateそれぞれのリクエスト処理を記述しています。
const onSubmit = async (data: TodoItem) => {
if (item.id) {
// 更新リクエスト
await axios.put(`${apiURL}${item.id}`, data);
} else {
// 登録(作成)リクエスト
await axios.post(apiURL, data);
}
// 更新していたら一覧を更新してから編集状態を解除
onCompleted(true);
};
Create(作成)とUpdate(更新)の切り分けは、送信する内容にidがあるかで判断します。
idがない場合、json-server側で登録時に自動採番されます。
onCompleted()は、jsonが更新されていたら、Read(読み取り)を実行してToDo一覧を更新します。
その際、編集タスクのIDを管理している(State)setEditItemIdをリセットして編集状態を解除します。
onCompleted={async (isUpdated) => {
// 更新していたら一覧を更新してから編集状態を解除
if (isUpdated) {
await fetchTodoItems();
}
setEditItemId(undefined);
}}
Delete(削除)
削除は、タスクに完了のチェックが入っている場合だけ可能にしました。
削除ボタンをクリックした時に、タスクが完了になっているのをチェックしてからDelete(削除)のリクエストを送信します。
<button
type="submit"
disabled={editItemId !== undefined}
onClick={async () => {
if (item.isTodoFinish === false) {
alert("完了していないタスクは削除できません。");
return;
}
const deleteDialog = confirm(
`「${item.todoItem}」を削除しますか?`
);
if (deleteDialog) {
await axios.delete(
`${apiURL}${item.id}`
);
await fetchTodoItems();
}
}}
>
削除
</button>
Delete(削除)リクエストを送信したら、Read(読み取り)を実行してToDoリストを更新します。
以上でCRUDの動きが一通り完了しました!
まとめ
単純なToDoアプリですが、ReactのReact Hook Formを使ってのフォーム作成、zodを使ったバリデーションなど色々なライブラリを使って作成しました。TypeScriptで作成したのでさらに難しかったです。
API必須のPromiseについても、内容を理解しないとうまくタスク更新できないので、今回のアプリは基礎とはいえ改めて勉強になりました、、。