【解決】Gatsby + Firestoreでコメント欄機能を自作しようとしてドツボ…
【追記】
どちらが原因かわからないが、2つのことを変更してうまくいった。
①スラグをとってくる方法を
import { useLocation } from "@reach/router"
から
import { globalHistory as history } from "@reach/router"
にした。
import { globalHistory as history } from "@reach/router
const { location, navigate } = history
const pathname = location.pathname
const _location = pathname.replace(/\//g, '') // スラッシュを削除
const slug = _location
②FirestoreのTimestamp型のフィールドをとってくるのに、ひと癖ふた癖あった。
あかんコード(createdがタイムスタンプ型フィールド):
{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>
改善コード:
{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
###なぜなのだ?
まあ以下はコメントデータをFirestoreからページごとに読み込むコードなのだが。
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)
でフィルターしたデータを取ってくるようにしている。
どうもうまくいかない。
ページ内での移動だと問題ないが、ブラウザの戻るボタンで移動するとこんなエラー。
TypeError: unsubscribe is not a function
おそらくlifecycleとの関連だろうか(参考:useBackButton hook to handle back button behavior in React Native)
コンテキストを使ってみたり、onSnapshopを使わずgetでとりにいくようにしてみたり、slugを親からpropsで渡してみたり…。いろいろやったが非常に困窮を極めた。Reactとは違うのだよReactとは!ということか…。
参考:GatsbyとWordPressを使用したウェブサイト構築の概要(高速で静的)より「ダイナミックコンテンツの組み込みがないGatsbyを使用すると、ダイナミックコンテンツの管理方法と配信方法を再考する必要があります。つまり、静的と動的のハイブリッドを作成しなければなりません」
プラグインを使ってみよう
Gatsbyはプラグインが豊富でFirestore用にもgatsby-source-firestore
というプラグインがある。
これでやってみよう。
インストールと設定方法は以下の通り。
参考:gatsby-source-firestore
そしてgatsby-config.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,
}),
},
],
},
},
],
}
コメント表示コンポーネントは以下の通り。
// 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
これでうまくいった。イケそうだ!
しかし残念なことにビルドエラー・・・
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の設置方法について。
不毛なドツボ作業ではあったがとても良い勉強になったかもしれない。