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

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

### 環境構築編

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

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

https://qiita.com/naoyuki2/items/7474d448de7769905d82
</details>

# 今回のゴール
前回挿入したテストデータをブラウザに表示しよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582046/b6e24c35-cd0c-7e83-0078-aebb7e6fdc43.png)

# １．Prismaインスタンスの作成

Prismaインスタンスをリロードするたびに生成するのはサーバさんが大変なので、一回だけ生成してそれを使い回すという方法(`シングルトン`)を使いたいと思う。

### シングルトンとは

> シングルトン (singleton) は、一度だけインスタンス化でき、グローバルにアクセスできるようなクラスのことです。この単一のインスタンスをアプリケーション全体で共有できることから、シングルトンはアプリケーションのグローバルな状態を管理するのに適しています。

https://zenn.dev/morinokami/books/learning-patterns-1/viewer/singleton-pattern

その`シングルトン`を記述するために`lib/prisma.ts`ファイルを作成し、下記のように記述する。

```tsx:src/app/api/lib/prisma.ts
import { PrismaClient } from '@prisma/client'

let prisma: PrismaClient

const globalForPrisma = global as unknown as {
    prisma: PrismaClient | undefined
}

if (!globalForPrisma.prisma) {
    globalForPrisma.prisma = new PrismaClient()
}
prisma = globalForPrisma.prisma

export default prisma
```
> if (!globalForPrisma.prisma) でグローバルオブジェクトに prisma プロパティが存在しないかチェックし、存在しない場合に new PrismaClient() で新しいインスタンスを作成し、グローバルオブジェクトに割り当てているのだ。

> グローバルオブジェクトを使用することで、ホットリロードしても何回も何回もprismaのインスタンスが作成されなくて済む、ということなのだ。

https://qiita.com/mamimami0709/items/7ce5e26afea1fded0747#nextauthjs%E3%81%A7%E8%AA%8D%E8%A8%BC%E6%A9%9F%E8%83%BD%E3%81%AE%E5%AE%9F%E8%A3%85

# ２．APIの作成(GETメソッド)
### GETメソッドの記述
`src/app/api/todo/route.ts`にGETメソッドを記述していく。
```ts:src/app/api/todo/route.ts
import { todo } from '@prisma/client'
import prisma from '../lib/prisma'
import { NextResponse } from 'next/server'

export async function GET() {
    // todoテーブルから全件取得
    try {
        const todos: todo[] = await prisma.todo.findMany()
        return NextResponse.json(todos)
    } catch (error) {
        return NextResponse.json(error)
    }
}
```
### 一応コードの解説
```ts:src/app/api/todo/route.tsのimportのところ
import { todo } from '@prisma/client' // 1
import prisma from '../lib/prisma' // 2
import { NextResponse } from 'next/server' // 3
```
1. todo
`todo`を`@prisma/client`からimportしているが、これは`todo`テーブルの型のこと。[前回の記事](https://qiita.com/naoyuki2/items/7474d448de7769905d82)で`npx prisma generate
`を実行したときにこの型ができているらしい。自分で書かなくていいからありがたいね。
2. prisma
上の方でprismaインスタンスをグローバルに定義しているためどこからでもimportで使用できる。
3. NextResponse
普通の`Response`を拡張した特殊なレスポンスを返せるやつ。正直今回のコードではありがたみが分からないが使ってみたかったから入れた。

https://nextjs.org/docs/app/api-reference/functions/next-response

```ts:src/app/api/todo/route.tsのGET関数のところ
export async function GET() {
    // todoテーブルから全件取得
    try {
        const todos: todo[] = await prisma.todo.findMany()
        return NextResponse.json(todos)
    } catch (error) {
        return NextResponse.json(error)
    }
}
```

非同期処理でデータを取得し、それを返すだけの関数。

`prisma.todo`は、`prisma`で`todo`テーブルを扱うという意味。

`findMany`は条件に合う全てのレコードを取得してくる。

今回は条件がないためSQLでいう`SELECT * FROM todo`と同じ結果が返ってくる。

### 環境変数の設定
上記のようなフォルダ構成にしたため、APIルートはローカルの場合`http://localhost:3000/api`となる。一応これを`.env`ファイルに`NEXT_PUBLIC_API_URL`という名前で書いておく。
```:env
NEXT_PUBLIC_API_URL = "http://localhost:3000/api"
```

# ３．見た目の作成

今回はスタイリングが目的ではないのでデザインは気にせず書いていく。決してデザインができないからではない。

```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[]>([])
    useEffect(() => {
        const getTodo = async () => {
            const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/todo`)
            const data = await res.json()
            console.log(data)
            setTodos(data)
        }
        getTodo()
    }, [])
    return (
        <>
            {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>
                </div>
            ))}
        </>
    )
}

```
### 一応コードの解説
そんな解説いらないって思われるかも知れないけど、自分のような初学者の方に向けて細かく区切って解説する。

```tsx:src/app/page.tsxの１行目
'use client'
```

最近のNext.jsのアップデートにより、作ったファイルがデフォルトで全てサーバーコンポーネントになったらしい。でも、`useState`や`useEffect`はクライアントコンポーネントでしか使えないので先頭に`use client`という記述をして、クライアントコンポーネントにしている。

```tsx:src/app/page.tsxのimportの部分
import { useEffect, useState } from 'react'
import { todo } from '@prisma/client'
```

`useEffect`や`useState`の解説は省きます。

２行目でインポートしている`todo`は上で解説したので省略。

```tsx:src/app/page.tsxのimportの部分
const [todos, setTodos] = useState<todo[]>([])
useEffect(() => {
    const getTodo = async () => {
        const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/todo`)
        const data = await res.json()
        setTodos(data)
    }
    getTodo()
}, [])
```

受け取ったデータは`useState`で管理することにした。型は先ほどインポートした`todo`を使用する。また、配列で受け取るため、`[]`をつけている。

初回レンダリング時にフェッチしてほしいため`useEffect`を使用している。フェッチ先のURLは`.env`ファイルに書いた`NEXT_PUBLIC_API_URL`に`/todo`を使って指定する。

:::note warn
データフェッチに`useEffect`を使用するのはあまりよろしくないらしいが、その改善はまた別の記事にしたいと思う。
:::

```tsx:src/app/page.tsxのreturn以降の部分
    return (
        <>
            {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>
                </div>
            ))}
        </>
    )
```

２点だけ解説する。

`map`関数を使うときは`key`を設定しないと、変更があったときにReactさんが大変なので識別できるように一意の値をつける。

https://just-dooo-it.vercel.app/entry/react-map-key

`createdAt`と`updatedAt`を`todo.createdAt`と表示しようとすると`型 'Date' を型 'ReactNode' に割り当てることはできません`というエラーが出る。どうやらDate型のオブジェクトはそのままでは表示できないらしい。そのため、４つ解決法を書く。

１．toString
`String型`にキャストできる。でも正直見ずらい。
```tsx:
<p>{todo.createdAt.toString()}</p>
// 2024-01-18T01:05:33.061Z
```
２．toLocaleString
日付と時間が表示できる。見やすくなったね。
```tsx:
<p>{new Date(todo.createdAt).toLocaleString()}</p>
// 2024/1/18 10:05:33
```
３．toLocaleDateString
日付のみ表示できる。時間はいらないっていうケース結構ありそうだもんね。
```tsx:
<p>{new Date(todo.createdAt).toLocaleDateString()}</p>
// 2024/1/18
```

４．toLocaleTimeString
時間のみ表示できる。使い時はあまり分からない。
```tsx:
<p>{new Date(todo.createdAt).toLocaleTimeString()}</p>
// 10:05:33
```

# ４．表示できているか見てみよう
下記のコマンドでローカルサーバを立ち上げてアクセスしよう。
```shell:ターミナル
npm run dev
```

下の画像のようになっていれば成功だ！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582046/b732f597-df30-c250-7064-c2a2b50e5fb1.png)

# おわりに

記事書きながら勉強するの大変だけど楽しい

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

https://zenn.dev/morinokami/books/learning-patterns-1/viewer/singleton-pattern

https://zenn.dev/ikura1/articles/a1e91ce077d2c4d585dc

https://qiita.com/mamimami0709/items/7ce5e26afea1fded0747#nextauthjs%E3%81%A7%E8%AA%8D%E8%A8%BC%E6%A9%9F%E8%83%BD%E3%81%AE%E5%AE%9F%E8%A3%85

https://www.prisma.io/docs/orm/more/help-and-troubleshooting/help-articles/nextjs-prisma-client-dev-practices

https://www.prisma.io/docs/orm/reference/prisma-client-reference#findmany