【Next.js】Next.js13でフォームの値をAPI経由で送受信をやってみる
概要
Next.jsがバージョン13から大幅に構成が変わり、これまでpages
フォルダで色々ページ作ってたりしましたが、app
ディレクトリーになりPages RouterからApp Routerになりました。
Next.js 13の変更点について
Next.js 13の変更点について簡単に触れておく、これまでPagesフォルダ内にabout.js
などファイルを作って、useEffect
などのReactHooksでデータを取得していたりしましたが、今後はAppディレクトリとなりPagesフォルダは消えました。
またAppディレクトリ配下にページを作成する場合は、ServerComponentという扱いになり、ReactHookが使えません。その場合、"use Client"をファイル内で宣言して、ClientComponentsとして扱う必要があり、データの取得などはServerComponets*、クリックなど処理をするにはClientComponentsというすみ分けになった。
今回、そんな中でもフォームの内容をクリックして送信して、受け取った値をページに表示させることをやってみる。
というのも前回、MongoDBを使ってデータを登録という記事を書いたが、api周りも変更されていたので、それで今回調べてみました。
今回はフォームに標準を当ててるので、Next.js 13の基本的な変更点については書きません。
API仕様変更について
Next.js 13になって変更になったAPI周り。
ディレクトリ構成
APIはNext.jsのプロジェクトを作成すると、pages/api/hello.js
といったディレクトリ構成になっていた。
ただ今回のアップデートで、app/api/hello/route.js
となり、route.jsというファイルが必要となる。例えばユーザーに関するAPIの処理をしたいのであれば、app/api/user/route.js
というディレクトリ構成となる。
ファイルの中身
以前のバージョンと今回のバージョンで書く方法も変更になった模様。
以前、フォームの値を受けった場合は下記のように書いていた。
(req.body.name
はフォームのname="name"としている場合)
import React from "react";
const Post = (req, res) => {
try {
return res.status(200).json({ message: "送信成功", name: req.body.name });
} catch (error) {
return res.status(400).json({ message: "送信失敗" });
}
};
export default Post;
引数にrequest
とresponse
があって、request
でフォームの値を受け取って、たとえばMongoDBとか使ってるのであれば、接続して追加するなどの処理など。
新しいバージョンだと下記のように記載するのをよくネットで見る。
export async function GET(request) {
const { searchParams } = new URL(request.url)
console.log("GET request", searchParams.get("name"))
return new Response(JSON.stringify({ message: "Hello World" }))
}
export async function POST(request) {
const body =await request.json()
console.log("POST request", body.name)
return new Response(JSON.stringify({ message: "Hello World" }))
}
新しいバージョンだとasync
の非同期処理をしている。
実装
簡単な例としてフォームに入力したテキストをapiに送信。受け取ったテキストに文字列を追加してレスポンスで返してフロント側に表示させる。
今回のDEMOはこちら
実装にあたって、以下のディレクトリー構成にする。
- app/page.js
- app/api/post/route.js
page.js
まずはフォームの部分です。
今回はクリックなどの処理やuseState
を使用しているため、use Client
を宣言してClientComponentsとしています。
"use client"
import { useState } from 'react'
export default function Home() {
const [name, setName] = useState('')
const [postedData, setPostedData] = useState('')
const onChangeHandler = (e) => {
setName(e.target.value)
}
const onSubmitHandler = async (e) => {
e.preventDefault()
const res = await fetch('/api/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name }),
})
const data = await res.json()
setPostedData(data.body)
}
return (
<main>
<h1>Next JS APIのテスト</h1>
<form onSubmit={onSubmitHandler} className='flex flex-col justify-center' action='/api/form' method='POST'>
<input value={name} onChange={onChangeHandler} type='text' name='name' placeholder='名前' />
<button type='submit'>送信</button>
</form>
<p>APIから受け取った値{postedData}</p>
</div>
</main>
)
}
あまり説明はいらないと思うが、フォームに入力した値をsetName
で保存して、クリックしたらfetch()
で/api/post
へname
のデータを送っている。
route.js
フォームで入力した値を受け取る。route.js
では以下のような処理。
今回は受け取った値に*POSTで受け取った値:*という文字列を追加してフロント側にレスポンスしてみる。
import { NextRequest } from "next/server"
export async function POST(request:NextRequest) {
const body =await request.json()
const returnBody =`POSTで受け取った値:${body.name}`
return new Response(JSON.stringify({ body:returnBody}))
}
今回フォームの値を送信としてるのでPOST
としている。
受け取ったデータをいったjson()
メソッドでJSON形式のテキスをJavaScriptのオブジェクトに変換し、その値にテキストを追加して新しい文字列を作成します。
以前のバージョンだとreq.body
で値を取り出していたが、新しいバージョンだとreq.json()
で取得できる。
その新しい文字列を返したいので、HTTPレスポンスオブジェクトを手動で作成するために使用されるnew Response
にJSON.stringify()
でJSONに変換した文字列を返します。
route.jsでの処理はここまで。
あとはフロント側のpage.tsx
でレスポンスを受けって表示させるだけです。
以下の部分ですね。
const onSubmitHandler = async (e) => {
e.preventDefault()
const res = await fetch('/api/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name }),
})
//ここでAPIからデータを受けとって
//setPostedDataでpostedDataに追加している。
const data = await res.json()
setPostedData(data.body)
}
参考サイト
Discussion