【Nuxt3】ちょっとしたタスク管理アプリを作ってみる(編集編)

# サンプルイメージ

<b>前回記事はこちら↓</b>

https://qiita.com/SKY-HaYaTo/items/a3d32441bd315c0eb083


下記のようなアプリを作ってみたいと思います↓

<b>【操作手順】</b>
一覧画面から編集したいタスクのID列をクリックする。
<b>①一覧画面(進捗が「完了」のタスクの場合)</b>
![vue1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/e828ddcd-6ea0-285c-1721-ba2100f63a71.png)

一覧画面で進捗が完了しているタスクなら、編集画面のチェックボックスはON表示なっている
<b>②編集画面</b>
![vue3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/a8931471-6a06-6298-75d3-2669ff23bbad.png)

逆に、一覧画面で進捗が完了しているタスクなら、編集画面のチェックボックスはOFF表示なっている
<b>②編集画面(進捗が「まだ」のタスクの場合)</b>
![vue4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/94c2180a-b7ec-7c3a-cb6e-9b25667d2414.png)

# ディレクトリ構造
前回記事と同じくディレクトリはこちらになります↓

```
プロジェクト名
├── pages/
│   ├── index.vue
│   └── detail.vue
├── server/
│   └── api/
│       ├── create.ts
│       └── task.ts
└── prisma/
    ├── migrate
    └── schema.prisma

```

# 画面側の(detail.vue)のコード
Vueテンプレートの場合、`v-model`といった双方向バインディング機能を活用して遷移先のページに値が渡されるようにしています。


```pages/detail.vue
<script setup lang="ts">
import {useRoute} from 'vue-router'

// 現在のルート情報を取得
const route = useRoute();

// クエリパラメータからidを取得
const taskId = route.query.id as number; // ここで型アサーションを加える;
const {data:task,error} = await useAsyncData('taskDetail',() => $fetch(`/api/selectTargetItem?id=${taskId}`));
console.log("取得したJSONデータは、" + JSON.stringify(task));

</script>
<template>
    <div>
        <h1>タスクの詳細ページ</h1>
            <!-- データが正常に取得できた場合 -->
        <div v-if="task">
            <form @submit.prevent="editTask" class="bg-gray-100 border border-black-300 max-w-sm mx-auto px-4 py-4">
                <div>
                    <input type="hidden" v-model="task.id"/>
                </div>
                <div class="mb-5">
                    <input type="text" v-model="task.task" 
                        class="bg-gray-50 border border-gray-300 text-lg rounded-sm w-full"/>
                </div>
                <div class="mb-5">
                    <input type="checkbox" 
                        v-model="task.completed" 
                        :checked="task.completed === true"/>
                </div>
                <button type="submit" class="bg-blue-500 text-white font-bold px-4 py-4 rounded-lg">更新する</button>
            </form>
        </div>
        <div class="flex justify-center py-3">
            <a href="/" 
                class="bg-green-500 text-white font-bold px-4 py-4 rounded-lg">
                一覧に戻る
            </a>
        </div> 
    </div>
    
    
</template>
```
# サーバ側のコード

```server/api/selectTargetItem.ts
import { PrismaClient } from '@prisma/client'

export default defineEventHandler(async (event) => {
  // クエリパラメータからtaskIdを取得
  const { id } = getQuery(event)

  // idがstring型の場合、数値に変換
  const taskId = parseInt(id as string)

  if (isNaN(taskId)) {
    return { error: '無効なIDです' }
  }

  const prisma = new PrismaClient()

  try {
      // Prismaを使用してtaskIdに対応するデータを取得
      const task = await prisma.task.findUnique({
        where: {
          id: taskId,
        },
      })

      if (!task) {
        return { error: 'データが見つかりません' }
      }

      return task
  } catch (error) {
      return { error: error.message }
  } finally {
      // Prismaのクライアントを切断
      await prisma.$disconnect()  
  }
})
```

# ChatGPTからのアドバイス
## 画面側のコードに関するアドバイス
エラーの原因は、<input>タグのvalue属性に対する記述方法にあります。

具体的には、以下のコード部分です：

```sample.vue
<input type="text" v-model="task" value=${{task.task}}/>

```

ここで、value=${{task.task}}と記述していますが、Vueのテンプレートでは、{{ }}は文字列挿入のシンタックスであり、value属性に直接使うことは適切ではありません。さらに、v-modelを使って双方向バインディングを行っているため、value属性は不要です。

解決方法
value属性を削除して、v-modelだけを使うようにします。
taskがオブジェクトである場合（task.taskのような構造）、v-modelは文字列や単純な値に直接バインディングされるべきですが、もしtaskがオブジェクトである場合は、適切なプロパティにバインドする必要があります。
修正後のコードは以下のようになります。

<b>修正後のコード：</b>

```sample.vue
<script setup lang="ts">
import { useRoute } from 'vue-router'

// 現在のルート情報を取得
const route = useRoute();

// クエリパラメータからidを取得
const taskId = route.query.id as number; // 型アサーション
const { data: task, error } = await useAsyncData('taskDetail', () => $fetch(`/api/selectTargetItem?id=${taskId}`));
console.log("取得したJSONデータは、" + JSON.stringify(task));
</script>

<template>
    <div>
        <h1>タスクの詳細ページ</h1>
        <h2>タスクのID値は、{{ taskId }}</h2>
        <!-- データが正常に取得できた場合 -->
        <div v-if="task">
            <form @submit.prevent="aa">
                <div>
                    <!-- `task.task` にバインディング -->
                    <input type="text" v-model="task.task" />
                </div>
                <div>
                    <!-- 完了状態を表示・変更できるようにする -->
                    <input type="checkbox" v-model="task.completed" :checked="task.completed === true" />
                </div>
            </form>
            <p>タスク名: {{ task.task }}</p>
            <p>完了: {{ task.completed }}</p>
        </div>
    </div>
</template>

```

変更点：
value=${{task.task}}を削除し、v-model="task.task"を使用しています。
チェックボックスには、v-model="task.completed"を使用して、完了状態を双方向バインディングしています。

## サーバ側のアドバイス
Nuxt3でサーバー側のTypeScriptでtaskIdを取得する方法について説明します。まず、Vueコンポーネントで$fetchを使ってAPIを呼び出し、taskIdをクエリパラメータとして渡します。その後、サーバー側でこのtaskIdを取得し、データを処理する方法を示します。

1. VueコンポーネントからtaskIdを渡す
まず、クライアントサイド（Vueコンポーネント）からAPIを呼び出す際に、taskIdをクエリパラメータとして渡します。
<script setup lang="ts">
import { useRoute } from 'vue-router'

// 現在のルート情報を取得
const route = useRoute()

// クエリパラメータからIDを取得
const taskId = route.query.id as string | undefined

// 非同期でAPIを呼び出してデータを取得
const { data: task, error } = await useAsyncData('taskDetail', () =>
  $fetch(`/api/selectTargetItem?id=${taskId}`)
)

if (error) {
  console.error('データ取得エラー:', error)
}
</script>

<template>
  <div>
    <h1>タスクの詳細ページ</h1>
    <h2>タスクID: {{ taskId }}</h2>

    <div v-if="task">
      <p>タスク名: {{ task.title }}</p>
      <p>完了: {{ task.completed }}</p>
    </div>

    <div v-else>
      <p>タスクが見つかりません。</p>
    </div>
  </div>
</template>

2. サーバーサイドでtaskIdを取得する方法
サーバーサイドでtaskIdを取得するには、APIハンドラー内でgetQueryを使ってクエリパラメータからtaskIdを取り出します。Nuxt3のAPIルートは、server/apiディレクトリ内に定義されます。

以下に、taskIdを取得してPrismaを使用するサンプルコードを示します。
```sample.ts
import { PrismaClient } from '@prisma/client'

export default defineEventHandler(async (event) => {
  // クエリパラメータからtaskIdを取得
  const { id } = getQuery(event)

  // idがstring型の場合、数値に変換
  const taskId = parseInt(id as string)

  if (isNaN(taskId)) {
    return { error: '無効なIDです' }
  }

  const prisma = new PrismaClient()

  try {
    // Prismaを使用してtaskIdに対応するデータを取得
    const task = await prisma.task.findUnique({
      where: {
        id: taskId,
      },
    })

    if (!task) {
      return { error: 'データが見つかりません' }
    }

    return task
  } catch (error) {
    return { error: error.message }
  } finally {
    await prisma.$disconnect()  // Prismaのクライアントを切断
  }
})
```

<b>3. ポイント</b>
getQuery(event)：これにより、HTTPリクエストのクエリパラメータを取得できます。この例では、id（taskId）を取得します。
クエリパラメータとして渡されるidは通常string型なので、parseIntでnumber型に変換します。
prisma.task.findUniqueを使って、取得したtaskIdを基にデータベースからタスクデータを取得します。
<b>4. クエリパラメータの取得方法</b>
クエリパラメータは、URLの?以降に渡されるキーと値のペアです。例えば、/api/selectTargetItem?id=123というURLでid=123を渡します。
getQueryはそのidを取得し、TypeScriptで扱えるようにします。
<b>まとめ</b>
クライアントサイドでtaskIdをクエリパラメータとして渡すために、$fetchを使用します。
サーバーサイド（server/api/selectTargetItem.ts）で、getQuery(event)を使ってtaskIdを取得し、その値を数値に変換してPrismaでデータベースを操作します。
この方法で、Nuxt3のサーバーサイドでtaskIdを取得し、データベースから該当するデータを取得することができます。