1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2024-01-18

はじめに

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

今までの記事

環境構築編

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

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

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

今回のゴール

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

image.png

1.Prismaインスタンスの作成

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

シングルトンとは

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

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

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のインスタンスが作成されなくて済む、ということなのだ。

2.APIの作成(GETメソッド)

GETメソッドの記述

src/app/api/todo/route.tsにGETメソッドを記述していく。

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)
    }
}

一応コードの解説

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テーブルの型のこと。前回の記事npx prisma generate を実行したときにこの型ができているらしい。自分で書かなくていいからありがたいね。
  2. prisma
    上の方でprismaインスタンスをグローバルに定義しているためどこからでもimportで使用できる。
  3. NextResponse
    普通のResponseを拡張した特殊なレスポンスを返せるやつ。正直今回のコードではありがたみが分からないが使ってみたかったから入れた。

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は、prismatodoテーブルを扱うという意味。

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

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

環境変数の設定

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

env
NEXT_PUBLIC_API_URL = "http://localhost:3000/api"

3.見た目の作成

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

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>
            ))}
        </>
    )
}

一応コードの解説

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

src/app/page.tsxの1行目
'use client'

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

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

useEffectuseStateの解説は省きます。

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

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を使って指定する。

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

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>
            ))}
        </>
    )

2点だけ解説する。

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

createdAtupdatedAttodo.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

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

image.png

おわりに

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

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?