このブログにはいいねボタンの機能がページ最下部にあります。
いいねボタンの仕組みや追加方法については、下記のページをご参照下さい。
このいいねボタンにはしばらく問題がありました。いいねボタンを押しても、ページをリロードしないといいね数が反映されないという問題です。
この問題をツイートしたところ、easy-notino-blog の開発者のおとよさんが下記の様なアドバイスをくれました。
上記のヒントを元にいいねボタンを押すといいね数が即座に反映するように修正できたので、今回はその内容についての記事となります。
実際の動作は以下の動画です。右側のページでいいねボタンを押すと、左側の Like というプロパティの数が一つ増えます。その後、何度もいいねボタンを押してもいいね数に変化はありません。
修正内容
まずは、src/pages/api/like.ts
を修正します。
変更前
getPostBySlug(slug as string)
.then(post => {
if (!post) throw new Error(`post not found. slug: ${slug}`)
return post
})
.then(post => incrementLikes(post))
.then(() => {
res.statusCode = 200
res.end()
})
.catch(e => {
console.log(e)
res.statusCode = 500
res.end()
})
変更後
try {
const post = await getPostBySlug(slug as string)
if (!post) {
throw new Error(`post not found. slug: ${slug}`)
}
await incrementLikes(post)
res.statusCode = 200
res.end()
} catch (e) {
console.log(e)
res.statusCode = 500
res.end()
}
ポイントとしては、
getPostBySlug(slug as string)
では更新前のLike
数を含んだpost
が返ってくる。incrementLikes(post)
では更新後のLike
数を含んだpost
が返ってくる。await
を使わないとPromise { <pending> }
が返ってきてうまくいかない。
というところです。
incrementLikes(post)
はLike
数を更新する関数なので、incrementLikes()
にpost
を渡すことでLike
数を更新したpost
が返ってきます。
続いて、src/components/like-button.tsx
を修正します。
変更前
const LikeButton = (props: Props) => {
const [active, setActive] = useState(false)
const handleClick = () => {
if (!active) {
axios.put(`/api/like?slug=${props.slug}`, {})
setActive(true)
}
}
変更後
const LikeButton = (props: Props) => {
const [active, setActive] = useState(false)
const [like, setLike] = useState(props.post)
const handleClick = () => {
if (!active) {
axios.put(`/api/like?slug=${props.slug}`, {})
setActive(true)
setLike((like) => like + 1)
}
}
ポイントとしては、
props
に 更新後のLike
数が入って渡ってくるので、useState
関数を使ってLike
数をstate
として管理することで、Like
数が変化したタイミングで再レンダリングする。
というところです。
ちなみにstate
の宣言は、以下の様に行います。
const [ state変数, state変数を更新するための関数 ] = useState( state変数の初期値 )
state
の更新は、state変数を更新するための関数
を使用することで行います。
state変数を更新するための関数
には、更新前のstate
の値が渡され、その結果を返します。
今回の場合は、以下の部分でLike
数をstate
として管理しています。
//stetaの宣言
const [like, setLike] = useState(props.post)
・
・
・
//stateの更新
setLike((like) => like + 1)
これで、Like
数が変化したタイミングで再レンダリングをすることができるようになりました。
ちなみに、この部分には別のuseState
関数も使われています。
const [active, setActive] = useState(false)
const handleClick = () => {
if (!active) {
・
・
・
setActive(true)
・
・
・
}
}
上記のようにactive
をstate
として管理してif
文で条件分岐することで、初回はactive
がfalse
なのでLike
数の更新を実行、2回目以降はactive
がtrue
なのでLike
数の更新を実行しないという処理が実現できています。
少しだけ問題点
いいねボタンを押していいね数が更新されても、すぐにリロードするといいね数が元に戻ってしまいます。
これは このブログが ISR を使っていることが原因です。ISR は一定期間が経つまでは古いキャッシュを返すので、リロードすると更新前のいいね数が反映されます。しかし、Notion 側のいいね数は更新されているので、一定期間たってリロードすると Notion のデータベースに入っているいいね数が表示されます(このブログの場合は、一定期間 = 60秒 に設定されています。)
いいね数を押した後にリロードするという動作はなかなかない動作だと思うので、今回は良しとします。
終わりに
今回の対応は、おとよさんのアドバイスのおかげで何とか実現することができました。
実現はできたのですが、実は後でhoromiさん( easy-notion-blog のコミュニティ運営)が同じようなことを実現していることに気づき、最終は horomi さんのコードをかなりマネさせてもらいました。
easy-notion-blog 関連のカスタマイズで困ったことがある場合は、おとよさんか horomi さんのリポジトリを参考に見てみるのがおすすめです。