Next.js の SG, ISR (with Apollo) の仕組みを作りながら学んでみた（ユーザーの詳細ページのコンポーネント作成）4/5

## はじめに

Next.js の SG + ISR (with Apollo) の仕組みを作りながら学んでみたシリーズ

このシリーズでは、Hasuraで用意した仮想のユーザーデータを外部APIとして取得しながら、

ユーザーの一覧ページと詳細ページを作りながら、SGと ISRを学ぶ記事です。

https://qiita.com/ryosuketter/items/ab7d4c8bfb19185f7b1e

今回は、ユーザーの詳細ページのコンポーネント作成の実装について書きます。

## 目次

- 実装
    - 一覧ページ
        - [getStaticProps](https://qiita.com/ryosuketter/items/442db08856093e6cfeea)
        - [必要なモジュールのimport](https://qiita.com/ryosuketter/items/8c4820c06a441c717180)
        - [interface作成](https://qiita.com/ryosuketter/items/8c4820c06a441c717180)
        - [コンポーネント作成](https://qiita.com/ryosuketter/items/8c4820c06a441c717180)
    - 詳細ページ
        - [getStaticProps と getStaticPaths](https://qiita.com/ryosuketter/items/799d3330ab7cda37145c)
        - [必要なモジュールのimport](https://qiita.com/ryosuketter/items/cb2507559c6c71bf9f17) 👈 今回はコレ
        - [interface作成](https://qiita.com/ryosuketter/items/cb2507559c6c71bf9f17) 👈 今回はコレ
        - [コンポーネント作成](https://qiita.com/ryosuketter/items/cb2507559c6c71bf9f17) 👈 今回はコレ
- 検証
    - [作ったものをブラウザで見ながらSGとISRの仕組みをざっくり理解する](https://qiita.com/ryosuketter/items/af45c292249ea7a88d9d)

## SGという用語に関して

SSGと言ったり、SGと言ったりするかもしれませんが、SSGはかつての呼び方なのでSG（Static Generate）に統一して書こうと思います。

仕組みは同じものだと思ってください。

## 最終的なゴール

以下のような構成のアプリを作ることです。

![スクリーンショット 2021-10-18 17.27.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/e8a9a2e1-2723-4fec-77ef-be3b9eefdd81.png)

### 目的

- 仕事で使っている技術のキャッチアップと復習
- 使う可能性がある技術の理解度向上

## 詳細ページに必要なファイル作成

```
pages/
└── users/
    └── [id].tsx
```

なんでこの位置に？なんでブラケット付き？と言う疑問はこちらで👇

https://qiita.com/ryosuketter/items/442db08856093e6cfeea#dynamic-routes%E3%81%A8%E3%81%AF

## 必要なモジュールのimport

```tsx
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`は`<<`的なアイコンです。

![Paul.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/33f2677b-1766-3b79-5dac-dc537978745c.png)

## interface作成

前回実装した関数`getStaticProps`からreturnされる `users`

ここね👇

```tsx
return {
  props: { users: data.users },
  revalidate: 1,
}
```

https://qiita.com/ryosuketter/items/442db08856093e6cfeea

この`users`は今回実装するコンポーネントのpropsに渡ってきます。

そのpropsの型をinterfaceで定義します。

```tsx
interface Props {
  user: {
    __typename?: 'users'
  } & Pick<Users, 'id' | 'name'>
}
```

中身がオブジェクトになっていて、typesが`users`、`id``name`が定義されます。

Usersをホバーすると全体感把握できますが、その中から必要なものをPick UP して`id`と`name`って感じです。

![スクリーンショット 2021-11-21 8.59.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/08b7ba55-1def-d498-ae36-732a21363d91.png)

実際に以下の記事（Hasura）で作成してきたユーザーデータを

https://qiita.com/ryosuketter/items/ab7d4c8bfb19185f7b1e

一覧ページで関数`getStaticProps`経由で取得した結果を1つ1つ詳細ページで表現した感じです。

```tsx
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通りですね。

## コンポーネント作成

```tsx
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`が存在しない場合

```tsx
if (!user) {
  return <Layout title="loading">Loading...</Layout>
}
```

### `user`が存在する場合

```tsx
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>
)
```

:::note warn
新たにSGするファイルを作る際やgetStaticPropsとgetStaticPathsを作った結果をデバッグしたい時は
yarn build してからブラウザで確認してください
:::

```
$ yarn build
$ yarn start
```

## 作成したファイル

<details><summary>長いので折り畳みました。</summary><div>

```tsx:pages/users/[id].tsx
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,
  }
}
```

</div></details>

## まとめ

今回は、ユーザーの詳細ページのコンポーネント作成の実装について書きました。

## 次回

https://qiita.com/ryosuketter/items/af45c292249ea7a88d9d

## アウトプット100本ノック実施中

https://qiita.com/ryosuketter/items/713474dbd009870e4103