1import { doc, getDoc, runTransaction } from 'firebase/firestore';
2import { HiHeart, HiOutlineHeart } from 'react-icons/hi';
3import { useDebouncedCallback } from 'use-debounce';
4import useSWR from 'swr';
5import { db } from '../firebase/client';
6import { Post } from '../types/post';
7
8type Props = {
9 userId: string;
10 postId: string;
11};
12
13const Like = ({
14 userId = 'MOCK_USER_ID',
15 postId = '5BlpSpkJhCcVnsi9NuW4',
16}: Props) => {
17 const postRef = doc(db, `posts/${postId}`);
18
19 const postSWR = useSWR<Post>(`posts/${postId}`, async () => {
20 const snap = await getDoc(postRef);
21 return snap.data() as Post;
22 });
23
24 const likeRef = doc(db, `posts/${postId}/likes/${userId}`);
25
26 const isLikeSWR = useSWR<boolean>(
27 `posts/${postId}/likes/${userId}`,
28 async () => {
29 const snap = await getDoc(likeRef);
30 return snap.exists();
31 }
32 );
33
34 const isLike = isLikeSWR.data;
35 const oldData = postSWR.data;
36
37 const toggleLike = useDebouncedCallback(async () => {
38 if (isLike === undefined || !oldData) {
39 return null;
40 }
41
42 postSWR.mutate(
43 {
44 ...oldData,
45 likeCount: oldData.likeCount + (isLike ? -1 : 1),
46 },
47 {
48 revalidate: false,
49 }
50 );
51
52 isLikeSWR.mutate(!isLike, {
53 revalidate: false,
54 });
55
56 runTransaction(db, async (transaction) => {
57 const likeDoc = await transaction.get(likeRef);
58 const postData = (await transaction.get(postRef)).data() as Post;
59
60 if (likeDoc.exists()) {
61 transaction.delete(likeRef);
62 } else {
63 transaction.set(likeRef, {
64 userId,
65 });
66 }
67
68 const oldLikeCount = postData.likeCount || 0;
69
70 return transaction.update(postRef, {
71 likeCount: oldLikeCount + (likeDoc.exists() ? -1 : 1),
72 });
73 });
74 }, 500);
75
76 return (
77 <div>
78 <button
79 className="inline-flex space-x-2 items-center"
80 onClick={toggleLike}
81 >
82 {isLike ? (
83 <HiHeart className="text-pink-500" size={20} />
84 ) : (
85 <HiOutlineHeart className="text-gray-500" size={20} />
86 )}
87 <span>いいね</span>
88 <span>{postSWR.data?.likeCount}</span>
89 </button>
90 </div>
91 );
92};
93
94export default Like;
いいねを行う場合以下の処理が発生します。
1
と同時に 該当記事のFirestoreドキュメント の likeCount
を +1
するいいねを外す場合、上記とは逆の処理が発生します。
1
と同時に 該当記事のFirestoreドキュメント の likeCount
を -1
する