目次
はじめに
前回まで
APIの呼び出し準備まで完了しました。
今回はコンポーネントを組み合わせて、レイアウトを構築していこうと思います。
APIも使います!
- 環境構築
- Storybookで管理しながらコンポーネントを作成
- APIの呼び出し
- コンポーネントを組み合わせてレイアウト構築
完成図
用意したコンポーネントを組み合わせて、完成図に近しいレイアウトを組んでいきます。

レイアウトを組んでいく
layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "../css/globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ja">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased h-screen`}>
{children}
</body>
</html>
);
}
``に下記のクラスを設定しました。
描画パーツが少ないので、画面の高さが狭まらないようにしたかったからです。
- h-screen →
height: 100vh
page.tsx
"use client";
import React, { useState } from "react";
import axios from "axios";
import { Header } from "../../components/header/Header";
import { Select } from "../../components/select/Select";
import { Input } from "../../components/input/Input";
import { Panel } from "../../components/panel/Panel";
import { Button } from "../../components/button/Button";
export default function Home() {
const selectOoptions = [
{ key: "digit7", value: "7", label: "7桁" },
{ key: "digit6", value: "6", label: "6桁" },
{ key: "digit5", value: "5", label: "5桁" },
{ key: "digit4", value: "4", label: "4桁" },
{ key: "digit3", value: "3", label: "3桁" },
{ key: "digit2", value: "2", label: "2桁" },
{ key: "digit1", value: "1", label: "1桁" },
];
const [min, setMin] = useState("0");
const [max, setMax] = useState("9");
const [digit, setDegit] = useState(selectOoptions[0].value);
const [drawTrigger, setDrawTrigger] = useState(false);
const [disabledDrawAll, setDisabledDrawAll] = useState(false);
const [resetTrigger, setResetTrigger] = useState(0);
const drawAll = () => {
setDrawTrigger(true);
setDisabledDrawAll(true);
};
const resetAll = () => {
setResetTrigger((prev) => prev + 1);
setDrawTrigger(false);
setDisabledDrawAll(false);
};
const onInputMinChange = (newInput: string) => {
setMin(newInput);
};
const onInputMaxChange = (newInput: string) => {
setMax(newInput);
};
const onSelectChange = (newDegit: string) => {
setDegit(newDegit);
};
const url = `/api/number?min=${min}&max=${max}&count=${digit}`;
const getNumber = async () => {
try {
const response = await axios.get(url);
return response.data[0];
} catch (error) {
console.error("数字の取得に失敗しました:", error);
return null;
}
};
return (
<div className="w-full h-full flex flex-col">
<Header />
<main className="grow grid justify-items-center items-center">
<div className="grid justify-items-center items-center gap-20">
<div className="grid grid-cols-3 gap-4">
<Select name="digit" label="桁数" options={selectOoptions} defaultValue={selectOoptions[0].value} onSelectChange={onSelectChange} />
<Input name="min" label="最小値" defaultValue={min} onInputChange={onInputMinChange} />
<Input name="max" label="最大値" defaultValue={max} onInputChange={onInputMaxChange} />
</div>
<div className="flex gap-4">
{Array.from({ length: parseInt(digit, 10) }).map((_, index) => (
<Panel key={`panel${index}`} getNumber={getNumber} drawTrigger={drawTrigger} resetTrigger={resetTrigger} />
))}
</div>
<div className="flex gap-8">
<Button primary={true} disabled={disabledDrawAll} label="一括抽選" size="large" onClick={drawAll} />
<Button primary={false} label="リセット" size="large" onClick={resetAll} />
</div>
</div>
</main>
</div>
);
}
長いので分割して説明したいと思います
一括抽選、リセット
const drawAll = () => {
setDrawTrigger(true);
setDisabledDrawAll(true);
};
const resetAll = () => {
setResetTrigger((prev) => prev + 1);
setDrawTrigger(false);
setDisabledDrawAll(false);
};
一括抽選、リセットの処理です。
リセットを押すごとにresetTriggerの数値が増やし、Panelコンポーネント側で検知してもらおうという考えです。
APIの呼び出し部分
const url = `/api/number?min=${min}&max=${max}&count=${digit}`;
const getNumber = async () => {
try {
const response = await axios.get(url);
return response.data[0];
} catch (error) {
console.error("数字の取得に失敗しました:", error);
return null;
}
};
前回用意したAPIルートの/api/numberにリクエストさせています。
JSX部分
return (
<div className="w-full h-full flex flex-col">
<Header />
<main className="grow grid justify-items-center items-center">
{/* 省略 */}
<Header />意外はクラスを設定しました。の領域に広げたかったので、grow
- grow →
flex-grow: 1
動作確認
ざっと下記を確認してみます
- 1つずつ抽選する
- 桁数を変更する
- 最小値を変更する
- 最大値を変更する
- 一括抽選する
- リセットする
(動画はカクついてるけど)想定どおりに動作しました!
おわりに
以上でNext.js+Tailwind CSS+Storybook でのアプリ作成は完了です。
とても長くなってしまいましたが、ここまでお付き合いいただきありがとうございました!
