ディップ Advent Calendar 2023 の19日目の記事です!
はじめに
はじめまして、私はディップ株式会社 R&D推進室に所属しております。
最近、業務でNext.jsを使用していたのですが、文献を調べても「あれ?記事とディレクトリ構成が違う...」となったり、記事通りに書いてもうまく表示されなかったりして、「なんかおかしいな」と悩んでいました。
理由を探ってみると、Next.jsのバージョンが13
となっており、仕様が12
からガラッと変わっており、ChatGPTに聞いてもなかなか解決できないことがあったりで開発に苦戦していました...
2022年10月26日にリリースされたばかりで、そもそもバージョン13
の記事があまり出なかったので、学習・アウトプットを兼ねてNext.js 13でアンケートフォームを作成してみました。
今回はその備忘録になります。
成果物
「何系エンジニアか」「好きな言語」を質問し、回答結果を集計してグラフで表示するプロジェクトになります。
こちらで公開していますので、ぜひご覧になってください。
アンケートフォーム
集計結果表示
Githubにも公開しています。立ち上げ方はreadme.mdをご参照ください。
環境構築
Next.js、MysqlをDockerの仮想環境に立てました。
version: '3.8'
services:
nextjs-app:
build: ./nextjs_questionnaire
ports:
- "3000:3000"
volumes:
- ./nextjs_questionnaire:/app # ファイルを直接マウント
depends_on:
- db
db:
image: mysql:8.0.35
environment:
MYSQL_ROOT_PASSWORD: your_root_password
MYSQL_DATABASE: your_database_name
MYSQL_USER: your_user
MYSQL_PASSWORD: your_password
ports:
- '3307:3306'
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
FROM node:18.17.0-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
データを格納するテーブルはsurvey_responsesとし、engineer_typeに「何系エンジニアか」、favorite_languageに「好きな言語」を格納するようにしています。
CREATE TABLE survey_responses (
id INT AUTO_INCREMENT PRIMARY KEY,
engineer_type VARCHAR(255) NOT NULL,
favorite_language VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
フォームの実装
ユーザが選択したデータを保存するための変数を用意します。
"use client"
// SurveyForm.tsx
import { useState } from 'react';
// use client
const SurveyForm: React.FC = () => {
const [engineerType, setEngineerType] = useState<string>(''); //何系エンジニアか
const [favoriteLanguage, setFavoriteLanguage] = useState<string>(''); //好きな言語
const [otherEngineer, setOtherEngineer] = useState<string>(''); //何系エンジニアかの質問でその他が選ばれた時に入力されるテキスト
const [otherLanguage, setOtherLanguage] = useState<string>(''); //好きな言語の質問でその他が選ばれた時に入力されるテキスト
const [isCompleted, setIsCompleted] = useState<boolean>(false); //送信ボタンを押した時、リクエストが成功したかどうか
...
Reactでは、宣言した変数の値を直接変えても一度描画したビューには反映されないため、useState
を利用して変数を宣言します。engineerType
の値を変更したいときは、以下のようにします。
setEngineerType("Webエンジニア");
また、useState
のようなものを利用してクライアントサイドでコンポーネントの状態が変わることが期待される場合、"use client"
を宣言しなければなりません。これを宣言したコンポーネント、及び派生のコンポーネントはクライアントサイドでuseState
のような状態を変更する関数が使えるようになります。
集計結果表示
フォームで回答いただいたデータを集計し、chart.jsを利用してグラフで表示します。
"use client";
import { useEffect, useState } from "react";
import { SurveyResponse } from "../api/survey/route"
import { Pie } from 'react-chartjs-2'
import { Chart, ArcElement, Tooltip, Legend } from 'chart.js'
Chart.register(ArcElement, Tooltip, Legend);
const SurveyChart = () => {
...
return (
<div style={{ width: 300, margin: '50px auto', display: 'flex', flexDirection: 'column', gap: 40 }}>
<div>
<span>回答数: {surveyData.length}</span>
</div>
<div>
<h2>エンジニア種別</h2>
<Pie data={chartDataEngineer} />
</div>
<div>
<h2>言語種別</h2>
<Pie data={chartDataLanguage} />
</div>
</div>
);
};
export default SurveyChart;
Next.js 13では、フロントで表示するコンポーネントをapp下に配置し、[任意のフォルダ名]/page.tsx
のような構成でコンポーネントを定義します。
この時、[任意のフォルダ名]
がURLのパスになり、page.tsx
内のexport default
に定義されたコンポーネントがそのパスで呼ばれるコンポーネントになります。
データ取得・保存API
フロントから送信されたフォームデータをMySQLに格納する処理、格納されたデータを取得する処理を定義しています。
import { NextResponse } from 'next/server';
import mysql from 'mysql2';
...
export async function POST(req: Request) {
...
}
export async function GET(req: Request) {
...
}
Next.js 13でAPIを定義したい場合、appディレクトリ配下に[任意のフォルダ名]/route.ts
という形式でファイルを保存します。この時、[任意のフォルダ名]
がURLのパスとなります。
このプロジェクトでは、/api/survey
がパスになっており、以下のURLでAPIリクエストができます。
https://nextjs-survey.yutaka-create.com/api/survey
また、POST・GET関数を定義することで、それぞれのリクエスト時の挙動を定義することができます。
最後に
今年初めてReact系のフレームワークに触れましたが、vueのライフサイクルより動きが追いやすくて覚えやすいと感じました。
とはいえ、use client
周りでまだ理解しきれていない部分もたくさんありますので、勉強してまた記事にしようと思います。
最後まで読んでいただき、ありがとうございました。