はじめに
Next.jsの知識を深めるために、Todoアプリを作ることにしました。そのメモを残しておきます。今回はデータ取得についてのメモを残します。何か間違っていることなどございましたらコメントいただけますと、とても喜びます。
今回のゴール
前回挿入したテストデータをブラウザに表示しよう。
1.Prismaインスタンスの作成
Prismaインスタンスをリロードするたびに生成するのはサーバさんが大変なので、一回だけ生成してそれを使い回すという方法(シングルトン
)を使いたいと思う。
シングルトンとは
シングルトン (singleton) は、一度だけインスタンス化でき、グローバルにアクセスできるようなクラスのことです。この単一のインスタンスをアプリケーション全体で共有できることから、シングルトンはアプリケーションのグローバルな状態を管理するのに適しています。
そのシングルトン
を記述するために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のインスタンスが作成されなくて済む、ということなのだ。
2.APIの作成(GETメソッド)
GETメソッドの記述
src/app/api/todo/route.ts
にGETメソッドを記述していく。
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)
}
}
一応コードの解説
import { todo } from '@prisma/client' // 1
import prisma from '../lib/prisma' // 2
import { NextResponse } from 'next/server' // 3
- todo
todo
を@prisma/client
からimportしているが、これはtodo
テーブルの型のこと。前回の記事でnpx prisma generate
を実行したときにこの型ができているらしい。自分で書かなくていいからありがたいね。 - prisma
上の方でprismaインスタンスをグローバルに定義しているためどこからでもimportで使用できる。 - NextResponse
普通のResponse
を拡張した特殊なレスポンスを返せるやつ。正直今回のコードではありがたみが分からないが使ってみたかったから入れた。
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
という名前で書いておく。
NEXT_PUBLIC_API_URL = "http://localhost:3000/api"
3.見た目の作成
今回はスタイリングが目的ではないのでデザインは気にせず書いていく。決してデザインができないからではない。
'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>
))}
</>
)
}
一応コードの解説
そんな解説いらないって思われるかも知れないけど、自分のような初学者の方に向けて細かく区切って解説する。
'use client'
最近のNext.jsのアップデートにより、作ったファイルがデフォルトで全てサーバーコンポーネントになったらしい。でも、useState
やuseEffect
はクライアントコンポーネントでしか使えないので先頭にuse client
という記述をして、クライアントコンポーネントにしている。
import { useEffect, useState } from 'react'
import { todo } from '@prisma/client'
useEffect
やuseState
の解説は省きます。
2行目でインポートしているtodo
は上で解説したので省略。
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
を使って指定する。
データフェッチにuseEffect
を使用するのはあまりよろしくないらしいが、その改善はまた別の記事にしたいと思う。
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>
))}
</>
)
2点だけ解説する。
map
関数を使うときはkey
を設定しないと、変更があったときにReactさんが大変なので識別できるように一意の値をつける。
createdAt
とupdatedAt
をtodo.createdAt
と表示しようとすると型 'Date' を型 'ReactNode' に割り当てることはできません
というエラーが出る。どうやらDate型のオブジェクトはそのままでは表示できないらしい。そのため、4つ解決法を書く。
1.toString
String型
にキャストできる。でも正直見ずらい。
<p>{todo.createdAt.toString()}</p>
// 2024-01-18T01:05:33.061Z
2.toLocaleString
日付と時間が表示できる。見やすくなったね。
<p>{new Date(todo.createdAt).toLocaleString()}</p>
// 2024/1/18 10:05:33
3.toLocaleDateString
日付のみ表示できる。時間はいらないっていうケース結構ありそうだもんね。
<p>{new Date(todo.createdAt).toLocaleDateString()}</p>
// 2024/1/18
4.toLocaleTimeString
時間のみ表示できる。使い時はあまり分からない。
<p>{new Date(todo.createdAt).toLocaleTimeString()}</p>
// 10:05:33
4.表示できているか見てみよう
下記のコマンドでローカルサーバを立ち上げてアクセスしよう。
npm run dev
下の画像のようになっていれば成功だ!
おわりに
記事書きながら勉強するの大変だけど楽しい
参考にさせていただいた記事