はじめに
Next.js の SG + ISR (with Apollo) の仕組みを作りながら学んでみたシリーズ
このシリーズでは、Hasuraで用意した仮想のユーザーデータを外部APIとして取得しながら、
ユーザーの一覧ページと詳細ページを作りながら、SGと ISRを学ぶ記事です。
今回は、ユーザーの詳細ページのコンポーネント作成の実装について書きます。
目次
- 実装
- 一覧ページ
- 詳細ページ
- getStaticProps と getStaticPaths
- 必要なモジュールのimport 👈 今回はコレ
- interface作成 👈 今回はコレ
- コンポーネント作成 👈 今回はコレ
- 検証
SGという用語に関して
SSGと言ったり、SGと言ったりするかもしれませんが、SSGはかつての呼び方なのでSG(Static Generate)に統一して書こうと思います。
仕組みは同じものだと思ってください。
最終的なゴール
以下のような構成のアプリを作ることです。
目的
- 仕事で使っている技術のキャッチアップと復習
- 使う可能性がある技術の理解度向上
詳細ページに必要なファイル作成
pages/
└── users/
└── [id].tsx
なんでこの位置に?なんでブラケット付き?と言う疑問はこちらで👇
必要なモジュールのimport
import { VFC } from 'react'
import Link from 'next/link' // 一覧ページへリンクするので
import { GetStaticProps, GetStaticPaths } from 'next' // 各関数の型
import { ChevronDoubleLeftIcon } from '@heroicons/react/solid'
import { initializeApollo } from '../../lib/apolloClient' // Hasuraにデータ取得するためのGraphQlクライアントメソッド
import { GET_USERSIDS, GET_USERBY_ID } from '../../queries/queries' // Hasuraにデータ取得するためのクエリ
import { GetUsersByIdQuery, GetUserIdsQuery, Users } from '../../types/generated/graphql' // クエリを書いたら自動生成される型情報
import { Layout } from '../../components/Layout'
ChevronDoubleLeftIcon
は<<
的なアイコンです。
interface作成
前回実装した関数getStaticProps
からreturnされる users
ここね👇
return {
props: { users: data.users },
revalidate: 1,
}
このusers
は今回実装するコンポーネントのpropsに渡ってきます。
そのpropsの型をinterfaceで定義します。
interface Props {
user: {
__typename?: 'users'
} & Pick<Users, 'id' | 'name'>
}
中身がオブジェクトになっていて、typesがusers
、id``name
が定義されます。
Usersをホバーすると全体感把握できますが、その中から必要なものをPick UP してid
とname
って感じです。
実際に以下の記事(Hasura)で作成してきたユーザーデータを
一覧ページで関数getStaticProps
経由で取得した結果を1つ1つ詳細ページで表現した感じです。
console.log(users)
これで見てみると
[
{
"__typename": "users",
"id": "5f172cd2-221c-4ea5-8b83-6420b18860ab",
"name": "lilly"
},
{
"__typename": "users",
"id": "0458dd7d-70a5-4087-88c7-d8c3ed51a505",
"name": "bob"
},
{
"__typename": "users",
"id": "f6a27de5-2f26-4e14-8435-bc463aabe791",
"name": "Paul"
}
]
interface通りですね。
コンポーネント作成
const UserDetail: VFC<Props> = ({ user }) => {
if (!user) {
return <Layout title="loading">Loading...</Layout>
}
return (
<Layout title={user.name}>
<p className="text-xl font-bold">User detail</p>
<p className="m-4">{`ID : ${user.id}`}</p>
<p className="mb-4 text-xl font-bold">{user.name}</p>
<Link href="/hasura-ssg">
<div className="flex cursor-pointer mt-12">
<ChevronDoubleLeftIcon
data-testid="auth-to-main"
className="h-5 w-5 mr-3 text-blue-500"
/>
<span data-testid="back-to-main">Back to main-ssg-page</span>
</div>
</Link>
</Layout>
)
}
export default UserDetail
以下の条件でコンポーネントを出し分けています。
user
が存在しない場合
if (!user) {
return <Layout title="loading">Loading...</Layout>
}
user
が存在する場合
return (
<Layout title={user.name}>
<p className="text-xl font-bold">User detail</p>
<p className="m-4">{`ID : ${user.id}`}</p>
<p className="mb-4 text-xl font-bold">{user.name}</p>
<Link href="/hasura-sg">
<div className="flex cursor-pointer mt-12">
<ChevronDoubleLeftIcon
data-testid="auth-to-main"
className="h-5 w-5 mr-3 text-blue-500"
/>
<span data-testid="back-to-main">Back to main-sg-page</span>
</div>
</Link>
</Layout>
)
新たにSGするファイルを作る際やgetStaticPropsとgetStaticPathsを作った結果をデバッグしたい時は
yarn build してからブラウザで確認してください
$ yarn build
$ yarn start
作成したファイル
長いので折り畳みました。
import { VFC } from 'react'
import Link from 'next/link'
import { GetStaticProps, GetStaticPaths } from 'next'
import { ChevronDoubleLeftIcon } from '@heroicons/react/solid'
import { initializeApollo } from '../../lib/apolloClient'
import { GET_USERSIDS, GET_USERBY_ID } from '../../queries/queries'
import {
GetUsersByIdQuery,
GetUserIdsQuery,
Users,
} from '../../types/generated/graphql'
import { Layout } from '../../components/Layout'
interface Props {
user: {
__typename?: 'users'
} & Pick<Users, 'id' | 'name'>
}
const UserDetail: VFC<Props> = ({ user }) => {
if (!user) {
return <Layout title="loading">Loading...</Layout>
}
return (
<Layout title={user.name}>
<p className="text-xl font-bold">User detail</p>
<p className="m-4">{`ID : ${user.id}`}</p>
<p className="mb-4 text-xl font-bold">{user.name}</p>
<Link href="/hasura-sg">
<div className="flex cursor-pointer mt-12">
<ChevronDoubleLeftIcon
data-testid="auth-to-main"
className="h-5 w-5 mr-3 text-blue-500"
/>
<span data-testid="back-to-main">Back to main-sg-page</span>
</div>
</Link>
</Layout>
)
}
export default UserDetail
export const getStaticPaths: GetStaticPaths = async () => {
const apolloClient = initializeApollo()
const { data } = await apolloClient.query<GetUserIdsQuery>({
query: GET_USERSIDS,
})
const paths = data.users.map((user) => ({
params: {
id: user.id,
},
}))
return {
paths,
fallback: true,
}
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const apolloClient = initializeApollo()
const { data } = await apolloClient.query<GetUsersByIdQuery>({
query: GET_USERBY_ID,
variables: { id: params.id },
})
return {
props: {
user: data.users_by_pk,
},
revalidate: 1,
}
}
まとめ
今回は、ユーザーの詳細ページのコンポーネント作成の実装について書きました。
次回
アウトプット100本ノック実施中