GatsbyでHello, worldからサイトを作ってみる(4) データの条件付き取得と詳細ページの作成

2019.6.8 12:00

概要

一覧ページは一つあれば良かったので一覧ページを修正したが、詳細はそもそもポートフォリオの数だけページが必要になるため、ページからして自動で生成する必要が出てくる。

そこで、今回はページが自動生成されるようにする方法を紹介したい。

Gatsby Node APIを使う

Gatsbyでは、ページを作成する処理の流れの中で、利用する側に何か追加の処理をする必要があるか聞いてくれる仕組みが備わっている。

利用する側で追加の処理をしてもらうためには、

  • 予め決められたファイル名(gatsby-node.js)のファイルをプロジェクト直下に作成する
  • 予め決められた名前で関数を実装してエクスポートする

の2点を守る必要がある。

4-1

今回は

  • 追加のページを作る

を行いたいので、それに対応するcreatePagesの実装を行う。

なお、他にもいろいろな追加処理ができるので、公式サイトもチェックしてみるとよいだろう。

createPagesの実装

流れとしては、graphqlでデータの取得を行い、取得結果のリストをforEachでループ処理しながら、createPageに必要なデータを渡して呼び出すという段取りである。

なお、graphqlでのデータの取得処理は非同期のためPromiseで返ってくるので、thenブロックの中で結果の処理を行う必要がある。

gatsby-node.js

const path = require('path');

exports.createPages = ({ graphql, actions /* 渡されるパラメータの中からgraphqlとactionsを利用する */ }) => {
  const { createPage } = actions; /* actionsの中で実装されているcreatePageの処理を利用する */
  const query = `
  {
    allPortfolioYaml {
      edges {
        node {
          slug
        }
      }
    }
  }
  `;
  return graphql(query).then(result => {
    result.data.allPortfolioYaml.edges.forEach(({ node }) => {
      createPage({
        /* pathでこのページにアクセスするためのURLを指定する */
        path: `portfolio/${node.slug}`,
        /* componentでこのページの土台となるテンプレートのパスを指定する */
        component: path.resolve(`./src/templates/detail.js`),
        /* contextにはページに渡したい追加情報を与える */
        context: {
          /* コンポーネント側でリクエストするデータのキーにする */
          slug: node.slug,
        },
      })
    })
  })
};

ここで、slugというキーワードが出てきたが、実は前回までのYamlファイルにはslugを定義していないので、定義してあげる必要がある。

portfolio/test1.yaml

+ slug: test-one
  title: test 1
  description: これはテストです。

portfolio/test2.yaml

+ slug: test-two
  title: test 2
  description: | 
    これはテストです。
    これはテストです。

これでGatsbyに追加のページを自動的に作成させることができるようになる。 ただし、今のままだとこのページの土台となるテンプレートファイルが足りず、実行するとエラーになる。

テンプレートの作成

足りないファイルの作成に取り掛かる。下記の手順で修正する

  1. srcのフォルダの中にtemplatesフォルダを作成する
  2. src/pagesの中にあるdetail.jstemplatesの中に移動する
  3. 内容を下記の通り書き換える

src/templates/detail.js

  import React from "react"
  import Layout from "../components/layout"
  import Detail from "../components/detail"
+ import {graphql} from "gatsby"

- export default () => (
+ export default ({data}) => (
  <Layout>
-     <Detail />
+     <Detail data={data} />
    </Layout>
  )

+ export const query = graphql`
+ /* ここでcreatePagesでcontextに渡したパラメータを受け取れる(ただし頭に$がつく) */
+ query($slug: String!) {
+   portfolioYaml(slug: { eq: $slug }) { # slug == $slugとなるyamlを取得する
+     title
+     description
+   }
+ }
+ `;

<Detail>dataを受け取れるようになったので、合わせて修正する。

src/components/detail.js

  import React from "react"

- export default () => (
+ export default ({ data }) => (
    <div>
-     Detail
+     <h1>{data.portfolioYaml.title}</h1>
+     <div dangerouslySetInnerHTML={{ __html: data.portfolioYaml.description.replace("\n", "<br />")}}/>
    </div>
  )

関連ファイルの修正

詳細ページのURLがポートフォリオごとに振られるようになったので、一覧ページのリンクも修正する。

src/pages/index.js

  import React from "react"
  import Layout from "../components/layout"
  import ItemList from "../components/item-list"
  import {graphql} from "gatsby"

  export default ({ data }) => (
    <Layout>
      <ItemList data={data} />
    </Layout>
  )

  export const query = graphql`
  query {
    allPortfolioYaml {
      edges {
        node {
+         slug
          title
          description
        }
      }
    }
  }
  `;

src/components/item-list.js

  import React from "react"

  import Item from "./item"

  import styles from "./item-list.module.css"

  export default ({ data }) => (
    <div className={styles.container}>
      {
        data.allPortfolioYaml.edges.map(({ node }) => {
-         return <Item key={node.id} id={node.id} title={node.title} description={node.description} />;
+         return <Item key={node.slug} slug={node.slug} title={node.title} description={node.description} />;
        })
      }
    </div>
  )

src/components/item.js

  import React from "react"
  import {Link} from "@reach/router";

- export default ({ title, description }) => (
+ export default ({ slug, title, description }) => (
-   <Link to={`/detail?id=${id}`}>
+   <Link to={`/portfolio/${slug}`}>
      <div>
        <strong>{title}</strong>
      </div>
      <div dangerouslySetInnerHTML={{ __html: description.replace("\n", "<br />")}}/>
    </Link>
  )

以上で、シンプルではあるが、ポートフォリオサイトを作成することができた。

gatsby