Next.js13でTodoリストを作ってみた!コードを詳しく解説
Next.is13を使ったTodoリストをYouTubeを見ながら作ってみました。が、理解が追い付かなかったので自分なりにまとめなおすことにしました。
この記事では、「Next.js13」「TypeScript」「React」を学ぶことができます。
Next.js13でTodoリストを作ってみた!コードを詳しく解説
todoアプリのファイル構造
- フォルダ構造:
- Todoアプリは、pages、components、api、typesの4つの主要なフォルダで構成されています。
pages
: ページコンポーネントが格納されます。components
: 再利用可能なUIコンポーネントが格納されます。api
: データ取得用のAPI関数が格納されます。types
: アプリで使用される型定義が格納されます。
- ページコンポーネント:
pages
フォルダ内にpage.tsx
があり、これがTodoアプリのメインページとなります。page.tsx
では、Todoリストの表示とタスクの追加が行われます。
- コンポーネント:
components
フォルダ内には、アプリで使用される再利用可能なコンポーネントが格納されます。- 例えば、Todoの追加フォーム(
AddTask.tsx
)、Todoリスト(TodoList.tsx
)などがあります。
- API関数:
api
フォルダ内には、外部APIからデータを取得するための関数が格納されます。- 例えば、Todoリストを取得する
getAllTodos
関数がここに含まれます。
- 型定義:
types
フォルダ内には、アプリケーション全体で使用される型定義が格納されます。- 例えば、Todoアイテムの型定義(
Task
)などが含まれます。
├── pages
│ └── page.tsx (Todoアプリのメインページ)
├── components
│ ├── AddTask.tsx (Todoの追加フォーム)
│ └── TodoList.tsx (Todoリスト)
├── api
│ └── api.ts (API関数のエクスポート)
└── types
└── types.ts (型定義)
これらの要素が相互に連携し、Todoアプリが動作します。`page.tsx`でAPI関数を呼び出してデータを取得し、取得したデータを`TodoList.tsx`で表示するようにコンポーネントが設計されています。`AddTask.tsx`では新しいタスクを追加するためのフォームが提供されます。
各コンポーネント間でのデータの受け渡し
+-------------------+
| page.tsx |
| (Main Page) |
+-------------------+
|
| (todos)
|
+-------------------+
| TodoList.tsx |
| (Todo List) |
+-------------------+
|
| (todos)
|
+-------------------+
| TodoItem.tsx |
| (Todo Item) |
+-------------------+
|
| (newTask)
|
+-------------------+
| AddTask.tsx |
| (Add Task Form) |
+-------------------+
- page.tsx (Main Page):
- Todoリスト全体の表示と、新しいタスクの追加フォームを提供します。
TodoList.tsx
コンポーネントにtodos
データを渡します。
- TodoList.tsx (Todo List):
TodoItem.tsx
コンポーネントに各Todoアイテムのデータを渡します。AddTask.tsx
コンポーネントに新しいタスクを追加するためのフォームを提供します。
- TodoItem.tsx (Todo Item):
- Todoリスト内の個々のTodoアイテムを表示します。
- AddTask.tsx (Add Task Form):
- 新しいタスクを追加するためのフォームを提供します。
- 追加されたタスクは
page.tsx
に戻され、Todoリストに反映されます。
このようにして、各コンポーネントがデータを受け取り、それを適切に処理して表示することで、Todoアプリが機能します。
ブラウザで表示とコンポーネントの関係
---------------------------
| Nextjs 13 Todo App | <- タイトル (Home)
| ---------------------- |
| | AddTask Component | | <- タスク追加フォーム (AddTask)
| ---------------------- |
| ---------------------- |
| | TodoList Component | | <- タスクリスト (TodoList)
| | ------------------ | |
| | | Todo Component | | | <- 各タスク (Todo)
| | ------------------ | |
| | ------------------ | |
| | | Todo Component | | |
| | ------------------ | |
| | ------------------ | |
| | | Todo Component | | |
| | ------------------ | |
| ---------------------- |
---------------------------
コード解説
page.tsx
Todoリスト全体の表示と、新しいタスクの追加フォームを提供します。TodoList.tsx
コンポーネントに todos
データを渡します。
src\app\page.tsx
import Image from "next/image";
import AddTask from "./components/AddTask";
import TodoList from "./components/TodoList";
import { getAllTodos } from "./api";
export default async function Home() {
const todos = await getAllTodos();
return (
<main className="flex flex-col items-center min-h-screen py-2 bg-gray-200 justify-center">
<h1 className="text-4xl font-mono text-gray-700 -mt-32">Nextjs 13 Todo App</h1>
<div className="w-full max-w-xl mt-5">
<div className="w-full px-8 py-6 bg-white shadow-md rounded-lg">
<AddTask />
<TodoList todos={todos} />
</div>
</div>
</main>
);
}
import Image from "next/image";
import AddTask from "./components/AddTask";
import TodoList from "./components/TodoList";
import { getAllTodos } from "./api";
Image
は Next.js の組み込みコンポーネントで、最適化された画像を提供します。この例では画像を使用していませんが、プロジェクトで画像を扱う際に役立ちます。AddTask
はタスク追加用のコンポーネントです。TodoList
はタスクリストを表示するためのコンポーネントです。getAllTodos
は全てのTodoアイテムを取得するための関数です。これはAPIからデータを取得する役割を持っています。
export default async function Home() {
const todos = await getAllTodos();
return (
<main className="flex flex-col items-center min-h-screen py-2 bg-gray-200 justify-center">
<h1 className="text-4xl font-mono text-gray-700 -mt-32">Nextjs 13 Todo App</h1>
<div className="w-full max-w-xl mt-5">
<div className="w-full px-8 py-6 bg-white shadow-md rounded-lg">
<AddTask />
<TodoList todos={todos} />
</div>
</div>
</main>
);
}
Home
関数は非同期関数として定義されています。これはgetAllTodos
からデータを取得するためです。getAllTodos
関数を呼び出し、取得したTodoリストをtodos
変数に格納しています。<main>
タグの中にアプリケーションの主要なコンテンツが含まれています。className
プロパティを使って、Tailwind CSS クラスを適用し、スタイルを整えていま<AddTask />
コンポーネントは新しいタスクを追加するためのフォームやボタンを表示します。<TodoList todos={todos} />
コンポーネントは、取得したtodos
をリスト表示します。
Home
関数は非同期関数として定義され、getAllTodos
関数はPromiseを返す非同期関数です。getAllTodos
関数が非同期でデータを取得するため、Home
コンポーネントが最初にレンダリングされたときにその結果を待つことになります。ここで重要なのは、非同期処理が完了するまで Home
コンポーネントのレンダリングが一時停止することです。
非同期処理とレンダリングの関係
①非同期処理が完了するまで Home コンポーネントのレンダリングが一時停止
②非同期処理が完了したら、その結果を使ってコンポーネントを再レンダリングして、最新の情報を表示互いに密接に関連していて、データの取得や更新を反映される際に重要
— もりだい@エンジニア挑戦中 (@moridai1104) May 18, 2024
AddTask.tsx
ユーザーが新しいタスクを入力し、「AddTask」ボタンをクリックしてタスクを追加するインターフェース
src\app\components\AddTask.tsx
import React from "react";
const AddTask = () => {
return (
<form className="mb-4 space-y-3">
<input type="text" className="w-full border px-4 py-2 rounded-lg focus:outline-none focus:border-blue-400" />
<button className="w-full px-4 py-2 text-white bg-blue-500 rounded transform hover:bg-blue-400 hover:scale-95 duration-200">AddTask</button>
</form>
);
};
export default AddTask;
import React from "react";
const AddTask = () => {
//~~~~~~~~~~~~~~~~~~~~~~~~~~
省略
//~~~~~~~~~~~~~~~~~~~~~~~~~~
};
export default AddTask;
- Reactをインポートしています。このファイルがReactコンポーネントを定義するためのものであることを示しています。
AddTask
という関数コンポーネントを定義しています。return
文の中で、このコンポーネントが返すJSXを定義しています。AddTask
コンポーネントをデフォルトエクスポートしています。これにより、他のファイルでこのコンポーネントをインポートして使用できます。
コードを理解するには子component→親componentがベスト
子component
・シンプルな機能と構造を理解
・親から渡されるデータがどのように使用されているかを理解
親component
・親がデータを管理。親→子へのデータの流れを理解
・複数の子がどのように組み合わさりアプリが構築されているかを把握— もりだい@エンジニア挑戦中 (@moridai1104) May 20, 2024
Todo.tsx
個々のTodoアイテムをリストアイテムとして表示します。アイテムのテキスト、編集ボタン、および削除ボタンを含んでいます。TodoList.tsx
の子コンポーネント
src\app\components\Todo.tsx
import React from "react";
import { Task } from "../types";
interface TodoProps {
todo: Task;
}
const Todo = ({ todo }: TodoProps) => {
return (
<li key={todo.id} className="flex justify-between p-4 bg-white border-l-4 border-blue-500 rounded shadow">
<span>{todo.text}</span>
<div>
<button className="text-green-500 mr-4">edit</button>
<button className="text-red-300">delete</button>
</div>
</li>
);
};
export default Todo;
import React from "react";
import { Task } from "../types";
React
をインポートしています。これにより、Reactの機能を使用してコンポーネントを定義できます。Task
型をインポートしています。これは../types
ファイルで定義されています。
interface TodoProps {
todo: Task;
}
TodoProps
というインターフェースを定義しています。これはTodo
コンポーネントが受け取るプロパティの型を指定しています。todo
プロパティはTask
型であることを示しています。
const Todo = ({ todo }: TodoProps) => {
return (
<li key={todo.id} className="flex justify-between p-4 bg-white border-l-4 border-blue-500 rounded shadow">
<span>{todo.text}</span>
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
省略
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
</li>
);
};
Todo
という関数コンポーネントを定義しています。- このコンポーネントは
todo
というプロパティを受け取り、それをTodoProps
インターフェースに基づいて型定義しています。 key={todo.id}
は、Reactがこのアイテムを一意に識別するためのキーです(ただし、ここでは必要ありません。map
メソッドを使用する親コンポーネントがキーを渡すため)。<span>
要素は、Todoアイテムのテキストを表示します。
export default Todo;
Todo
コンポーネントをデフォルトエクスポートしています。これにより、他のファイルでこのコンポーネントをインポートして使用できます。
TodoList.tsx
TodoList
コンポーネントは、todos
配列を受け取り、それぞれのTodoアイテムを Todo
コンポーネントとしてレンダリングします。これにより、TodoList
コンポーネントは、タスクリスト全体の表示を担当し、個々のタスクの詳細表示は Todo
コンポーネントに委ねています。
src\app\components\TodoList.tsx
import React, { useEffect } from "react";
import { Task } from "../types";
import Todo from "./Todo";
interface TodoListProps {
todos: Task[];
}
const TodoList = ({ todos }: TodoListProps) => {
return (
<ul className="space-y-3">
{todos.map((todo) => (
<Todo key={todo.id} todo={todo}/>
))}
</ul>
);
};
export default TodoList;
import React from "react";
import { Task } from "../types";
import Todo from "./Todo";
React
をインポートしています。このファイルがReactコンポーネントを定義するためのものであることを示しています。Task
型をインポートしています。これは../types
ファイルで定義されています。Todo
コンポーネントをインポートしています。これは個々のTodoアイテムを表示するためのコンポーネントです。
interface TodoListProps {
todos: Task[];
}
TodoListProps
というインターフェースを定義しています。これはTodoList
コンポーネントが受け取るプロパティの型を指定しています。todos
プロパティはTask
型の配列であることを示しています。
const TodoList = ({ todos }: TodoListProps) => {
return (
<ul className="space-y-3">
{todos.map((todo) => (
<Todo key={todo.id} todo={todo}/>
))}
</ul>
);
};
TodoList
という関数コンポーネントを定義しています。- このコンポーネントは
todos
というプロパティを受け取り、それをTodoListProps
インターフェースに基づいて型定義しています。 todos
配列の各要素に対してmap
メソッドを使用して、各TodoアイテムをTodo
コンポーネントとしてレンダリングします。Todo
コンポーネントにはkey
プロパティとtodo
プロパティを渡します。key={todo.id}
は、Reactが各アイテムを一意に識別するためのキーです。todo={todo}
は、Todo
コンポーネントにそのTodoアイテムのデータを渡します。
export default TodoList;
TodoList
コンポーネントをデフォルトエクスポートしています。これにより、他のファイルでこのコンポーネントをインポートして使用できます。
api.ts(getAllTodos、addTodo)
api.ts
ファイルは、サーバーからタスクデータを取得するための非同期関数を定義しています。このファイルは、getAllTodos
関数をエクスポートし、アプリケーションの他の部分で使用できるようにしています。
src\app\api.ts
import { Task } from "./types";
export const getAllTodos = async (): Promise<Task[]> => {
const res = await fetch('http://localhost:3001/tasks',{cache: "no-store",//SSR
});
const todos = res.json();
return todos;
};
export const addTodo = async (todo: Task): Promise<Task> => {
const res = await fetch('http://localhost:3001/tasks', {
method: "POST",
headers: {
"Content-Type":"application/json",
},
body: JSON.stringify(todo),
});
const newTodo = res.json();
return newTodo;
};
getAllTodos関数
import { Task } from "./types";
export const getAllTodos = async (): Promise<Task[]> => {
const res = await fetch('http://localhost:3001/tasks',{cache: "no-store",//SSR
});
const todos = res.json();
return todos;
};
fetch
関数: 指定されたURLからデータを取得します。cache: "no-store"
: SSR(サーバーサイドレンダリング)の場合、常に最新のデータを取得するためにキャッシュを無効にします。await res.json()
: レスポンスをJSON形式に解析し、JavaScriptオブジェクトに変換します。Promise<Task[]>
: タスクの配列を返すPromiseを返します。
addTodo関数
export const addTodo = async (todo: Task): Promise<Task> => {
const res = await fetch('http://localhost:3001/tasks', {
method: "POST",
headers: {
"Content-Type":"application/json",
},
body: JSON.stringify(todo),
});
const newTodo = res.json();
return newTodo;
};
fetch
関数: 指定されたURLに対してPOSTリクエストを送信します。
method: "POST"
: HTTPメソッドとしてPOSTを指定します。headers
: リクエストヘッダにコンテンツタイプとしてJSONを指定します。body: JSON.stringify(todo)
: タスクオブジェクトをJSON文字列に変換してリクエストボディに設定します。await res.json()
: レスポンスをJSON形式に解析し、新しく追加されたタスクをJavaScriptオブジェクトに変換します。Promise<Task>
: 新しく追加されたタスクを返すPromiseを返します。
AddTask.tsx (use client)
新しいタスクを追加するフォームを提供しています。ここでは、ユーザーがテキストを入力し、フォームを送信するとタスクが追加される流れになっています。
1. ユーザーが入力フィールドにタスクタイトルを入力
|
v
2. 入力フィールドの値が変わると setTaskTitle によって taskTitle ステートが更新される
|
v
3. ユーザーがフォームを送信
|
v
4. handleSubmit 関数が呼び出される
|
v
5. addTodo 関数を使って新しいタスクが追加される
|
v
6. サーバーにタスクデータが送信され、サーバーがレスポンスを返す
|
v
7. タスクが追加された後、入力フィールドがクリアされる
+--------------------------------------------------+
| ユーザーが入力フィールドにタスクタイトルを入力 |
| +--------------------------------------------+ |
| | <input value={taskTitle} onChange={...} /> | |
| +--------------------------------------------+ |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| 入力フィールドの値が変わると |
| setTaskTitle によって taskTitle ステートが更新 |
| +--------------------------------------------+ |
| | const [taskTitle, setTaskTitle] = | |
| | useState(""); | |
| +--------------------------------------------+ |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| ユーザーがフォームを送信 |
| +--------------------------------------------+ |
| | <form onSubmit={handleSubmit}> | |
| +--------------------------------------------+ |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| handleSubmit 関数が呼び出される |
| +--------------------------------------------+ |
| | const handleSubmit = async (e: FormEvent) =>| |
| | { e.preventDefault(); await addTodo(...); | |
| | setTaskTitle(""); } | |
| +--------------------------------------------+ |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| addTodo 関数を使って新しいタスクが追加される |
| +--------------------------------------------+ |
| | await addTodo({ id: uuidv4(), text: ... }) | |
| +--------------------------------------------+ |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| サーバーにタスクデータが送信され、サーバーがレスポンスを返す |
| +--------------------------------------------+ |
| | const res = await fetch('http://localhost:3001/tasks', { ... }) |
| | const newTodo = await res.json(); | |
| +--------------------------------------------+ |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| タスクが追加された後、入力フィールドがクリアされる |
| +--------------------------------------------+ |
| | setTaskTitle(""); | |
| +--------------------------------------------+ |
+--------------------------------------------------+
"use client";
import React, { ChangeEvent, FormEvent, useState } from "react";
import { addTodo } from "../api";
import { v4 as uuidv4 } from "uuid";
const AddTask = () => {
const [taskTitle, setTaskTitle] = useState("");
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
await addTodo({ id: uuidv4(), text: taskTitle });
setTaskTitle("");
};
return (
<form className="mb-4 space-y-3" onSubmit={handleSubmit}>
<input type="text" className="w-full border px-4 py-2 rounded-lg focus:outline-none focus:border-blue-400" onChange={(e: ChangeEvent<HTMLInputElement>) => setTaskTitle(e.target.value)}
value={taskTitle}
/>
<button className="w-full px-4 py-2 text-white bg-blue-500 rounded transform hover:bg-blue-400 hover:scale-95 duration-200">AddTask</button>
</form>
);
};
export default AddTask;
"use client";
importReact, { ChangeEvent, FormEvent, useState } from"react";
import { addTodo } from"../api";
import { v4asuuidv4 } from"uuid";
"use client";
: これはNext.jsの構文で、ファイルがクライアント側でレンダリングされることを示しています。React
とそのフックをインポートしています (ChangeEvent
,FormEvent
,useState
)。addTodo
関数を../api
からインポートしています。uuid
パッケージからuuidv4
をインポートし、ユニークなIDを生成するために使用しています。
const [taskTitle, setTaskTitle] = useState("");
useState
フック: フォーム入力の状態を管理します。taskTitle
は現在のタスクタイトルを保持し、setTaskTitle
はその値を更新する関数です。
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
await addTodo({ id: uuidv4(), text: taskTitle });
setTaskTitle("");
};
handleSubmit
関数: フォームが送信されたときに呼び出されます。e.preventDefault()
は、フォームのデフォルトの送信動作を防ぎます。addTodo
関数を呼び出し、新しいタスクを追加します。ここでuuidv4
によって生成されたユニークなIDとtaskTitle
を含むタスクオブジェクトを渡します。- タスクが追加された後、
taskTitle
を空文字列にリセットして、入力フィールドをクリアします。
return (
<form className="mb-4 space-y-3" onSubmit={handleSubmit}>
<input type="text" className="w-full border px-4 py-2 rounded-lg focus:outline-none focus:border-blue-400" onChange={(e: ChangeEvent<HTMLInputElement>) => setTaskTitle(e.target.value)}
value={taskTitle}
/>
<button className="w-full px-4 py-2 text-white bg-blue-500 rounded transform hover:bg-blue-400 hover:scale-95 duration-200">AddTask</button>
</form>
);
- フォーム (
<form>
):onSubmit
プロパティにhandleSubmit
関数を設定しています。 - 入力フィールド (
<input>
):type="text"
: テキスト入力フィールドです。className
: Tailwind CSS クラスを使用してスタイルを適用しています。onChange
: 入力フィールドの値が変わったときに呼び出される関数。入力された値をtaskTitle
に設定します。value
: 現在のtaskTitle
の値を表示します。
- ボタン (
<button>
):className
: Tailwind CSS クラスを使用してスタイルを適用しています。- ボタンをクリックするとフォームが送信されます。
api.ts(editTodo)
import { text } from "strem/consumers";
import { Task } from "./types";
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//省略
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export const editTodo = async (id: string, newText: string): Promise<Task> => {
const res = await fetch(`http://localhost:3001/tasks/$(id)`, {
method: "PUT",
headers: {
"Content-Type":"application/json",
},
body: JSON.stringify({ text: newText}),
});
const updateTodo = res.json();
return updateTodo;
};
- 目的: 既存のタスクを編集する。
- HTTPメソッド:
PUT
- リクエストURL: タスクのIDをURLの一部として渡す(
http://localhost:3001/tasks/${id}
)。 - リクエストヘッダー:
Content-Type: "application/json"
を設定。 - リクエストボディ: 新しいテキストを含むオブジェクトを
JSON.stringify
でJSON文字列に変換して送信。 - レスポンス処理: 更新されたタスクを
await res.json()
でパースし、updatedTodo
変数に格納して返します。
「`」「’」の違いに注意!
①バッククォート `
埋め込み式や変数を含む文字列を生成。${} を使って式や変数を埋め込みが可能
await fetch(`http://localhost:3001/tasks/${id}`
②シングルクォート’
単純な文字列を生成。変数や式を埋め込み不可
await fetch(‘http://localhost:3001/tasks/${id}’— もりだい@エンジニア挑戦中 (@moridai1104) June 2, 2024
Todo.tsx(編集機能の追加)
ユーザーが特定のタスク(ToDoアイテム)を編集できるようにするためのものです。
"use client";
import React, { useEffect, useRef, useState } from "react";
import { Task } from "../types";
import { editTodo } from "../api";
interface TodoProps {
todo: Task;
}
const Todo = ({ todo }: TodoProps) => {
const ref = useRef<HTMLInputElement>(null);
const [isEditing, setIsEditing] = useState(false);
const [editedTaskTitle, setEditedTaskTitle] = useState(todo.text);
useEffect(() => {
if (isEditing) {
ref.current?.focus();
}
}, [isEditing]);
const handleEdit = async () => {
setIsEditing(true);
};
const handleSave = async () => {
await editTodo(todo.id, editedTaskTitle);
setIsEditing(false);
};
return (
<li
key={todo.id}
className="flex justify-between p-4 bg-white border-l-4 border-blue-500 rounded shadow"
>
{isEditing ?(
<input
ref={ref}
type="text"
className="mr-2 px-2 rounded border-gray-400 border"
value={editedTaskTitle}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setEditedTaskTitle(e.target.value)
}
/>
) : (
<span>{todo.text}</span>
)}
<div>
{isEditing ? (
<button className="text-blue-500 mr-4" onClick={handleSave}>
save
</button>
) : (
<button className="text-green-500 mr-4" onClick={handleEdit}>
edit
</button>
)}
<button className="text-red-300">delete</button>
</div>
</li>
);
};
export default Todo;
"use client";
import React, { useEffect, useRef, useState } from "react";
import { Task } from "../types";
import { editTodo } from "../api";
use client
: これはNext.js 13のディレクティブで、このコンポーネントがクライアント側でレンダリングされることを示します。React, { useEffect, useRef, useState }
: Reactのフックをインポートしています。これらはコンポーネントの状態管理や副作用処理、DOM要素へのアクセスに使用されます。editTodo
: タスクを編集するためのAPI関数をインポートしています。
const Todo = ({ todo }: TodoProps) => {
const ref = useRef<HTMLInputElement>(null);
Todo
:Todo
コンポーネントはtodo
プロパティを受け取り、タスクを表示および編集します。ref = useRef<HTMLInputElement>(null)
: 編集モードの時に入力フィールドにフォーカスを設定するための参照を作成します。
const [isEditing, setIsEditing] = useState(false);
const [editedTaskTitle, setEditedTaskTitle] = useState(todo.text);
useEffect(() => {
if (isEditing) {
ref.current?.focus();
}
}, [isEditing]);
isEditing
: 編集モードかどうかを管理する状態フックです。editedTaskTitle
: 編集中のタスクのタイトルを保持する状態フックです。初期値はtodo.text
です。useEffect
:isEditing
がtrue
になったときに入力フィールドにフォーカスを設定します。
const handleEdit = async () => {
setIsEditing(true);
};
const handleSave = async () => {
await editTodo(todo.id, editedTaskTitle);
setIsEditing(false);
};
handleEdit
: 編集モードに切り替える関数です。handleSave
: 編集したタスクを保存し、編集モードを終了する関数です。editTodo
関数を呼び出してサーバーに更新を送信します。
return (
<li
key={todo.id}
className="flex justify-between p-4 bg-white border-l-4 border-blue-500 rounded shadow"
>
{isEditing ?(
<input
ref={ref}
type="text"
className="mr-2 px-2 rounded border-gray-400 border"
value={editedTaskTitle}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setEditedTaskTitle(e.target.value)
}
/>
) : (
<span>{todo.text}</span>
)}
<div>
{isEditing ? (
<button className="text-blue-500 mr-4" onClick={handleSave}>
save
</button>
) : (
<button className="text-green-500 mr-4" onClick={handleEdit}>
edit
</button>
)}
<button className="text-red-300">delete</button>
</div>
</li>
);
};
<li>
: タスクを表すリストアイテムです。スタイルが設定されています。- 編集モードの場合:
<input>
: タスクのタイトルを編集するための入力フィールドです。ref
を設定してフォーカスを管理し、value
はeditedTaskTitle
、onChange
イベントでeditedTaskTitle
を更新します。<button>
: 保存ボタンです。onClick
イベントでhandleSave
を呼び出します。
- 非編集モードの場合:
<span>
: タスクのタイトルを表示します。<button>
: 編集ボタンです。onClick
イベントでhandleEdit
を呼び出します。
<button>
: 削除ボタンです(現在は機能が実装されていません)。
Api.ts(削除ボタンを追加)
指定されたIDのタスクを削除するための非同期関数 deleteTodo
を定義しています。この関数はAPIリクエストを使用して、サーバー上のタスクを削除します。
import { text } from "stream/consumers";
import { Task } from "./types";
//~~~~~~~~~~~~~~~~~~~~~~~~~~
//省略
//~~~~~~~~~~~~~~~~~~~~~~~~~~
export const deleteTodo = async (id: string): Promise<Task> => {
const res = await fetch(`http://localhost:3001/tasks/${id}`, {
method: "DELETE",
headers: {
"Content-Type":"application/json",
},
});
const deleteTodo = res.json();
return deleteTodo;
};
export const deleteTodo = async (id: string): Promise<Task> => {
deleteTodo
: この関数はタスクの削除を行います。async
: 関数が非同期であり、Promiseを返すことを示します。id: string
: 関数が受け取る引数で、削除するタスクのIDです。Promise<Task>
: 関数の戻り値の型で、削除されたタスクの情報を含むPromiseです。
const res = await fetch(`http://localhost:3001/tasks/${id}`, {
method: "DELETE",
headers: {
"Content-Type":"application/json",
},
});
fetch
:fetch
関数を使用してHTTPリクエストを送信します。`http://localhost:3001/tasks/${id}`
: テンプレートリテラルを使用して、削除対象のタスクのIDをURLに組み込みます。これにより、特定のタスクに対してリクエストが送信されます。method: "DELETE"
: HTTPメソッドとしてDELETE
を指定し、サーバーに削除リクエストを送信します。headers
: リクエストヘッダーを設定し、Content-Type
をapplication/json
に指定します。これにより、リクエストの内容がJSON形式であることを示します。
const deleteTodo = await res.json();
res.json()
メソッドを呼び出すと、レスポンスボディがJSON形式であると仮定して、その内容をJavaScriptのオブジェクトとして解析(パース)します。このメソッドは非同期で実行されるため、await
を使って完了を待ちます。
todo.tsx(削除ボタンを追加)
タスク(Todo)を表示し、編集および削除する機能を持つコンポーネントを定義しています。削除機能に焦点を当てた部分の解説します。
"use client";
import React, { useEffect, useRef, useState } from "react";
import { Task } from "../types";
import { deleteTodo, editTodo } from "../api";
//~~~~~~~~~~~~~~~~~~~~~~~~
//省略
//~~~~~~~~~~~~~~~~~~~~~~~~
const handleDelete = async () => {
await deleteTodo(todo.id);
};
return (
//~~~~~~~~~~~~~~~~~~~~~~~~
//省略
//~~~~~~~~~~~~~~~~~~~~~~~~
<button className="text-red-300" onClick={handleDelete}>delete</button>
</div>
</li>
);
};
export default Todo;
import { deleteTodo, editTodo } from "../api";
deleteTodo
と編集する関数 editTodo
をインポートしています。これらの関数は別のファイル (../api
) で定義されています。 const handleDelete = async () => {
await deleteTodo(todo.id);
};
handleDelete
: タスクを削除するための関数です。await deleteTodo(todo.id)
:deleteTodo
関数を呼び出し、指定されたtodo.id
に基づいてタスクを削除します。この関数は非同期関数であるため、await
を使用して処理が完了するまで待ちます。
<button className="text-red-300" onClick={handleDelete}>delete</button>
onClick={handleDelete}
: ボタンがクリックされたときにhandleDelete
関数を呼び出します。これにより、タスクが削除されます。
まとめ
Todoリストって難しいですよね。
一からまとめ直すだけで学びになると思います。個人的にはYouTubeだけよりもまとめることが理解が相当深まりました。
Next.jsはpegeルーティングの理解がなかなか難しい印象です。何度も何度も学び直しました!