【04】Next.js14でTodoアプリ作成(DELETE編)

# はじめに

Next.jsの知識を深めるために、Todoアプリを作ることにしました。そのメモを残しておきます。今回はデータ削除についてのメモを残します。何か間違っていることなどございましたらコメントいただけますと、とても喜びます。

<details><summary>今までの記事</summary>
環境構築編

https://qiita.com/naoyuki2/items/e974c630c6cbd3c55254

【01】Next.js14でTodoアプリ作成(CREATE編)

https://qiita.com/naoyuki2/items/7474d448de7769905d82

【02】Next.js14でTodoアプリ作成(SELECT編)

https://qiita.com/naoyuki2/items/2d8ad09f767f818e8860

【03】Next.js14でTodoアプリ作成(INSERT編)

https://qiita.com/naoyuki2/items/27d550ab3766a1869aaf
</details>

# 今回のゴール

データの削除ができるようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582046/eb038613-4d31-38ca-30f8-e5c9ce8e2027.png)


# １．APIの作成(DELETEメソッド)

### DELETEメソッドの記述

今回は、ルートが`src/app/api/todo/[id]/route.ts`という場所にファイルを作成する。だが、見慣れない`[id]`というものが入っていると思う。これは`ダイナミックルーティング`と呼ばれるNext.jsの機能。

例えば、`http://localhost:3000/api/todo/123`でも`http://localhost:3000/api/todo/abc`でも、同じルートにアクセスすることになる。

> Next.js では [param] のようにしてページ名に角括弧(ブラケット)を使うことで動的なルーティング(別名 slug や pretty url など)を作成できます。

https://nextjs-ja-translation-docs.vercel.app/docs/routing/dynamic-routes

```tsx:src/app/api/todo/[id]/route.ts
export async function DELETE(
    request: Request,
    { params }: { params: { id: string } },
) {
    // todoテーブルのレコードを削除
    const id = Number(params.id)
    try {
        const todo: todo = await prisma.todo.delete({
            where: {
                id: id,
            },
        })
        return NextResponse.json(todo)
    } catch (error) {
        return NextResponse.json(error)
    }
}
```

### コードの解説

```tsx:上の方のコード
export async function DELETE(
    request: Request,
    { params }: { params: { id: string } },
) {
```

今回の場合は、`src/app/api/todo/[id]/route.ts`というファイルであるため、`[id]`の部分が動的(値が一定ではない)なパラメータとなる。

そのパラメータを受け取るために`params`というものを使用する。

`[id]`などのURLのパラメータは常に文字列として解釈される。そのため、型は`string`とする。

```tsx:
const id = Number(params.id)
```

上でも述べたように、フロントエンドから送信された`id`が数値であっても、APIルート内では文字列として受け取るため、`Number`で数値型にキャストしている。

APIルートが`[id]`であるため、`params.id`で`アクセスできる。

```tsx:
const todo: todo = await prisma.todo.delete({
    where: {
        id: id,
    },
})
```

`prisma`の`delete`関数を利用してレコードを削除する。どのレコードを削除するか指定するために、`where`句で条件を書く。今回は、`params`から取得した`id`と`todo`テーブルの`id`が一致するレコードを削除するというもの。`SQL`でいう`DELETE FROM todo WHERE id = 1`みたいなこと。

# ２．フロントエンドの実装

追加点は緑のところ。[前回の記事](https://qiita.com/naoyuki2/items/27d550ab3766a1869aaf#%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%85%A8%E4%BD%93)から`deleteTodo`関数、削除ボタンを追加した。

### コードの全体

```diff_tsx:src/app/page.tsx
'use client'

import { useEffect, useState } from 'react'
import { todo } from '@prisma/client'

export default function Home() {
    const [todos, setTodos] = useState<todo[]>([])

    const [inputVal, setInputVal] = useState<string>('')

    useEffect(() => {
        const getTodo = async () => {
            const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/todo`)
            const data = await res.json()
            setTodos(data)
        }
        getTodo()
    }, [])

    const postTodo = async () => {
        if (!inputVal) return alert('タイトルを入力してください')

        const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/todo`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                title: inputVal,
            }),
        })
        const data = await res.json()
        setTodos([...todos, data])
        setInputVal('')
    }

+   const deleteTodo = async (id: number) => {
+       const res = await fetch(
+           `${process.env.NEXT_PUBLIC_API_URL}/todo/${id}`,
+           {
+               method: 'DELETE',
+           },
+       )
+       const data = await res.json()
+       setTodos(todos.filter((todo) => todo.id !== data.id))
+   }

    return (
        <>
            <input
                placeholder="タイトル入力"
                value={inputVal}
                onChange={(e) => setInputVal(e.target.value)}
            />

            <button onClick={() => postTodo()}>追加</button>

            {todos.map((todo) => (
                <div key={todo.id}>
                    <p>{todo.id}</p>
                    <p>{todo.title}</p>
                    <p>{todo.content}</p>
                    <p>{todo.isDone}</p>
                    <p>{new Date(todo.createdAt).toLocaleString()}</p>
                    <p>{new Date(todo.updatedAt).toLocaleString()}</p>
+                   <button
+                       onClick={() => deleteTodo(todo.id)}
+                       className="bg-rose-400 px-2 py-1 rounded"
+                   >
+                       削除
+                   </button>
                </div>
            ))}
        </>
    )
}
```

### コードの解説

```tsx:deleteTodo関数のコード
const deleteTodo = async (id: number) => {
    const res = await fetch(
        `${process.env.NEXT_PUBLIC_API_URL}/todo/${id}`,
        {
            method: 'DELETE',
        },
    )
    const data = await res.json()
    setTodos(todos.filter((todo) => todo.id !== data.id))
}
```

受け取った`id`をもとに`DELETE`メソッドを呼び出す関数。

`filter`関数を使って、削除するレコード`以外`を`todos`に格納している。

> filterは配列の内容を指定した条件で絞り込む関数です。
配列の要素一つ一つを判定し、関数内でtrueが返ってきたもののみを抽出することができます。
また、この関数はArrayオブジェクトが持っている関数なので、配列に対してしか使用できません。

https://zenn.dev/fijii_rin/articles/34016e4e6aa4d0

```tsx:削除ボタンのとこ
<button
    onClick={() => deleteTodo(todo.id)}
    className="bg-rose-400 px-2 py-1 rounded"
>
    削除
</button>
```

`onClick`イベントで上で定義した`deleteTodo`を発火させる。その際に`todo.id`を渡す必要がある。

`tailwind css`でスタイリングをしているが今回は触れないでおく。

# ３．削除してみよう

赤い削除ボタンをクリックして消すことができればゴール。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582046/940411d8-de61-4273-ae4b-a15caea4ac08.png)


# おわりに

次回の`UPDATE`処理難しそう

# 参考にさせていただいた記事

https://zenn.dev/mostlove/articles/c9e6f1aa45ea09