【解決】Gatsby + Firestoreでコメント欄機能を自作しようとしてドツボ…

# 【解決】Gatsby + Firestoreでコメント欄機能を自作しようとしてドツボ…

【追記】
どちらが原因かわからないが、２つのことを変更してうまくいった。

①スラグをとってくる方法を
`import { useLocation } from "@reach/router"`
から
`import { globalHistory as history } from "@reach/router"`
にした。

```jsx

import { globalHistory as history } from "@reach/router

      const { location, navigate } = history
      const pathname = location.pathname
      const _location = pathname.replace(/\//g, '') // スラッシュを削除
      const slug = _location
```
<br />
②FirestoreのTimestamp型のフィールドをとってくるのに、ひと癖ふた癖あった。

あかんコード(createdがタイムスタンプ型フィールド）：

```jsx
 {listItem.map(item => (
     <tbody key={item.id}>
       <tr>
                  <td>{item.name}</td>
                  <td>{item.type}</td>
                  <td>{item.comment}</td>
                  <td>{item.slug}</td>
                  <td>{item.create}</td>
                  <td>
                      <button>Edit</button>
                      <button>Delete</button>
                  </td>
      </tr>
```

改善コード：

```jsx
{listItem.map(item => (
 <tbody key={item.id}>
  <tr>
          <td>{item.name}</td>
          <td>{item.type}</td>
          <td>{item.comment}</td>
          <td>{item.slug}</td>
          <td>{new Date(item.created.seconds * 1000).toLocaleDateString("ja-JP")} {new Date(item.created.seconds * 1000).toLocaleTimeString("ja-JP")}</td>
          <td>
              <button>Edit</button>
              <button>Delete</button>
          </td>
  </tr>
```


【追記ここまで】


---------------------------------------
動的ページで動的なクエリをこさえてFirestoreからコメントデータを取得・書き込みできる機能を実装しようとした。ステートフックとEffectを使用するやり方に沿って →[Gatsby+Firestoreでステートフックを利用したCRUD](https://qiita.com/atomyah/items/b004456d5c4552388a1d)

###なぜなのだ？

まあ以下はコメントデータをFirestoreからページごとに読み込むコードなのだが。

```jsx
import React, { useState, useEffect } from "react"
import firebase from "../utils/firebase"
import { AuthProvider } from '../contexts/auth'
import  AddCommentForm from './addcommentform'// コメント書き込みフォームコンポーネント<AddCommentForm />
import { useLocation } from "@reach/router"

const useComments = (pathname) => { // pathnameは '/article/10'とかいったような
  /*
    useLocation()で記事URLパスから作ったpathnameをもらってくる
   'article/10' ← こんなような文字列
   そしてpathnameフィールドに格納する.
  */
  const { location } = useLocation() // /article/10
  const _location = location.replace(/\//g, '') // スラッシュを削除
  const pathname = _location // article10

  const [comments, setComments] = useState([]);

  useEffect(() => {
      const unsubscribe = firebase
      firebase
        .firestore()
        .collection("comments")
        .where("pathname", "==", pathname) // URLパスをドキュメントデータに登録してある
        .orderBy("createdAt")
        .onSnapshot(snapshot => {
          const data = []
          snapshot.forEach(d => data.push({ 
              id: d.id,
              ...d.data(),
            }));
            setComments(data)
        });
      return () => unsubscribe(); 
  }, []);
  return comments;
};

・・・以下略（renderするところ）・・・
```

表示ページのslug（例えば`/articla/10`というようなURLパス）をuseLocation()で変数pathnameに格納し、`.where("pathname", "==", pathname)`でフィルターしたデータを取ってくるようにしている。

どうもうまくいかない。

ページ内での移動だと問題ないが、ブラウザの戻るボタンで移動するとこんなエラー。
<strong>TypeError: unsubscribe is not a function</strong>

おそらくlifecycleとの関連だろうか（参考：[useBackButton hook to handle back button behavior in React Native](https://medium.com/@Farzad_YZ/usebackbutton-hook-to-handle-back-button-behavior-in-react-native-d7666226d644)）

コンテキストを使ってみたり、onSnapshopを使わずgetでとりにいくようにしてみたり、slugを親からpropsで渡してみたり…。いろいろやったが非常に困窮を極めた。Reactとは違うのだよReactとは！ということか…。
参考：[GatsbyとWordPressを使用したウェブサイト構築の概要（高速で静的）](https://kinsta.com/jp/blog/gatsby-wordpress/)より「ダイナミックコンテンツの組み込みがないGatsbyを使用すると、ダイナミックコンテンツの管理方法と配信方法を再考する必要があります。つまり、静的と動的のハイブリッドを作成しなければなりません」


### プラグインを使ってみよう

Gatsbyはプラグインが豊富でFirestore用にも`gatsby-source-firestore`というプラグインがある。
これでやってみよう。

インストールと設定方法は以下の通り。
参考：[gatsby-source-firestore ](https://www.gatsbyjs.org/packages/gatsby-source-firestore/#gatsby-source-firestore-npm-version)

そしてgatsby-config.jsは以下の通り。

```js
// gatsby-config.js

・・・略・・・
{
        resolve: 'gatsby-source-firestore',
        options: {
          credential: require(`./credentials.json`),
          types: [
            {
              type: 'Comment',
              collection: 'comments',
              map: doc => ({
                name: doc.name,
                content: doc.content,
                createdAt: doc.createdAt,
                parentDid: doc.parentDid,
                path: doc.path,
                uid: doc.uid,              
              }),
            },
          ],
        },
      },
    ],
  }
```

コメント表示コンポーネントは以下の通り。

```jsx
// components/commentlist.jsx

import React, { useState, useEffect } from "react"
//import firebase from "../utils/firebase"
import { graphql, StaticQuery } from "gatsby"
import { AuthProvider } from './auth'
import { useLocation } from "@reach/router"
import AddForm from './addform'

const CommentList = () => {
  const { pathname } = useLocation()
  const _pathname = pathname.replace(/\//g, '') // スラッシュを削除
  
  return (
    <>
    <AddForm />
      <table className="tg">
          <tbody>
          <tr>
              <th>名前</th>
              <th>コメント</th>
              <th>uid</th>
              <th>ドキュメントId</th>
              <th>parentDid</th>
              <th>path</th>
              <th>日時</th>
          </tr>
          </tbody>

          <StaticQuery 
                query={graphql`
                    query {
                        comments: allComment {
                            edges {
                                node {
                                    id
                                    name
                                    content
                                    path
                                    parentDid
                                    createdAt
                                    uid
                                }
                            }
                        }
                    }
            `}
            render={(data) => {
            const comment = data.comments.edges.find(n => {
                return n.node.path == _pathname
                });
                console.log('■comment from GraphQL ',comment)
            if (!comment) { return null; }
              return (
                <tbody key>
                <tr>
                    <td>{comment.node.name}</td>
                    <td>{comment.node.content}</td>
                    <td>{comment.node.uid}</td>
                    <td>{comment.node.id}</td>
                    <td>{comment.node.parentId}</td>
                    <td>{comment.node.path}</td>
                    <td>{comment.node.createdAt}</td>
                    <td>
                        <button>返信</button>
                    </td>
                </tr>
                </tbody>
              )}
            }
           />
      </table>
    </>
  )
}
export default CommentList
```

これでうまくいった。イケそうだ！

しかし残念なことにビルドエラー・・・

```bash

The behavior for Date objects stored in Firestore is going to change
AND YOUR APP MAY BREAK.
To hide this warning and ensure your app does not break, you need to 
add the following code to your app before calling any other Cloud 
Firestore methods:

  const firestore = new Firestore();
  const settings = {/* your settings... */ timestampsInSnapshots: 
  true};
  firestore.settings(settings);

With this change, timestamps stored in Cloud Firestore will be read 
back as Firebase Timestamp objects instead of as system Date objects. 
So you will also need to update code expecting a Date to instead 
expect a Timestamp...
```

これはFirestoreのタイムスタンプフィールドの仕様が変わったことが原因。自分でコード書いてれば直せるがプラグインを使ってる以上、プラグインに対応してもらわないとどうしようもない。issueあがってるがご主人は放置状態のようだ。


### 結局

`Disqus`という外部APIサービスを知った。
はるかに簡単そうで多機能なのでこれを使うことにしようと思う。
次回、[Disqusの設置方法](https://qiita.com/atomyah/items/5f602da57b0e756c5864)について。


不毛なドツボ作業ではあったがとても良い勉強になったかもしれない。

<br />