結論
ユーザー情報がFireStoreから取得できず、実装できませんでした。
Firebase error : failed to get document because the client is offline
Firestoreのデータ設計
/users/{userId} {
uid: "abc123",
displayName: "たろう",
followers: ["uid1", "uid2"], // 自分をフォローしている人
following: ["uid5", "uid7"] // 自分がフォローしている人
}
フォローボタン機能の実装
import React, { useEffect, useState } from 'react';
import { db } from './firebase';
import {
collection,
query,
where,
getDocs,
addDoc,
deleteDoc,
doc,
} from 'firebase/firestore';
function FollowButton({ currentUserId, targetUserId }) {
const [isFollowing, setIsFollowing] = useState(false);
const [followDocId, setFollowDocId] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const checkFollow = async () => {
const q = query(
collection(db, 'followers'),
where('follower_id', '==', currentUserId),
where('followed_id', '==', targetUserId)
);
const snapshot = await getDocs(q);
if (!snapshot.empty) {
setIsFollowing(true);
setFollowDocId(snapshot.docs[0].id);
} else {
setIsFollowing(false);
setFollowDocId(null);
}
setLoading(false);
};
if (currentUserId && targetUserId && currentUserId !== targetUserId) {
checkFollow();
} else {
setLoading(false); // 自分自身は対象外
}
}, [currentUserId, targetUserId]);
const toggleFollow = async () => {
setLoading(true);
if (isFollowing && followDocId) {
await deleteDoc(doc(db, 'followers', followDocId));
setIsFollowing(false);
setFollowDocId(null);
} else {
const docRef = await addDoc(collection(db, 'followers'), {
follower_id: currentUserId,
followed_id: targetUserId,
});
setIsFollowing(true);
setFollowDocId(docRef.id);
}
setLoading(false);
};
if (loading || currentUserId === targetUserId) return null;
return (
<button
onClick={toggleFollow}
style={{
backgroundColor: isFollowing ? '#ccc' : '#007bff',
color: 'white',
padding: '0.5rem 1rem',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
marginLeft: '1rem',
}}
>
{isFollowing ? 'フォロー中' : 'フォロー'}
</button>
);
}
export default FollowButton;
- import React, { useEffect, useState } from 'react';
• 解説: Reactと、useEffectおよびuseStateのフックをインポートしています。useStateは状態管理、useEffectは副作用を管理するために使用します。
2. import { db } from './firebase';
• 解説: FirebaseのFirestoreデータベースをdbとしてインポートします。このdbを使ってFirestoreにアクセスします。
3. import { collection, query, where, getDocs, addDoc, deleteDoc, doc } from 'firebase/firestore';
• 解説: Firestoreの関数やモジュールをインポートします。
• collection: コレクションを参照するための関数。
• query: クエリを作成するための関数。
• where: 条件を指定するための関数。
• getDocs: クエリに基づくドキュメントを取得するための関数。
• addDoc: 新しいドキュメントをコレクションに追加するための関数。
• deleteDoc: 指定したドキュメントを削除するための関数。
• doc: 特定のドキュメントを参照するための関数。
4. function FollowButton({ currentUserId, targetUserId }) {
• 解説: FollowButtonコンポーネントを定義します。currentUserId(現在ログイン中のユーザーのID)とtargetUserId(フォローされるユーザーのID)をプロパティとして受け取ります。
5. const [isFollowing, setIsFollowing] = useState(false);
• 解説: isFollowingという状態を作成し、ユーザーがターゲットユーザーをフォローしているかどうかを管理します。初期値はfalse(フォローしていない状態)です。
6. const [followDocId, setFollowDocId] = useState(null);
• 解説: followDocIdという状態を作成し、フォロー関係を示すFirestoreドキュメントのIDを保持します。このIDは、フォローを解除する際にドキュメントを特定するために使います。
7. const [loading, setLoading] = useState(true);
• 解説: loadingという状態を作成し、データがロード中かどうかを管理します。初期値はtrueにして、データのチェックが完了するまでローディング状態にします。
8. useEffect(() => { ... }, [currentUserId, targetUserId]);
• 解説: useEffectを使って副作用を管理します。この中の処理はcurrentUserIdまたはtargetUserIdが変更されるたびに実行されます。 • 目的は、ターゲットユーザーが現在のユーザーに対してフォローされているかどうかを確認することです。
9. const checkFollow = async () => { ... };
• 解説: 非同期関数checkFollowを定義します。この関数は、Firestoreのfollowersコレクションをクエリして、現在のユーザーがターゲットユーザーをフォローしているかどうかを確認します。
10. const q = query(collection(db, 'followers'), where('follower_id', '==', currentUserId), where('followed_id', '==', targetUserId));
• 解説: followersコレクションから、follower_idがcurrentUserId、followed_idがtargetUserIdであるドキュメントを取得するクエリを作成します。
11. const snapshot = await getDocs(q);
• 解説: 上記のクエリを実行し、その結果をsnapshotとして取得します。
12. if (!snapshot.empty) { ... } else { ... }
• 解説: クエリ結果が空でない場合(すなわち、現在のユーザーがターゲットユーザーをフォローしている場合)は、isFollowingをtrueにし、followDocIdにドキュメントIDを設定します。空であれば、isFollowingをfalseにします。
13. setLoading(false);
• 解説: データの取得が終わったので、loadingをfalseに設定してローディング状態を終了します。
14. if (currentUserId && targetUserId && currentUserId !== targetUserId) { checkFollow(); } else { setLoading(false); }
• 解説: currentUserIdとtargetUserIdが有効かつ、両者が異なるユーザーである場合に、checkFollow関数を実行します。自分自身のフォローは不要なので、自己フォローの場合はすぐにloadingをfalseに設定します。
15. const toggleFollow = async () => { ... };
• 解説: toggleFollow関数を定義します。この関数はフォロー/フォロー解除のトグル処理を行います。
16. setLoading(true);
• 解説: フォロー/フォロー解除処理中にローディング状態にします。
17. if (isFollowing && followDocId) { await deleteDoc(doc(db, 'followers', followDocId)); setIsFollowing(false); setFollowDocId(null); } else { const docRef = await addDoc(collection(db, 'followers'), { follower_id: currentUserId, followed_id: targetUserId }); setIsFollowing(true); setFollowDocId(docRef.id); }
• 解説: isFollowingがtrueの場合はフォロー解除を行い、Firestoreから該当ドキュメントを削除します。isFollowingがfalseの場合はフォローを追加し、followersコレクションに新しいドキュメントを作成します。
18. setLoading(false);
• 解説: 処理が終了したらローディング状態を解除します。
19. if (loading || currentUserId === targetUserId) return null;
• 解説: loading状態がtrueの場合や、currentUserIdとtargetUserIdが同じ場合は、フォローボタンを表示しません。
20. return ( ... );
• 解説: 最後にフォローボタンを返します。ボタンの色は、isFollowingがtrueであれば灰色、falseであれば青色になります。また、ボタンにはフォロー中またはフォローというラベルが表示されます。
終わりに
このコンポーネントは、Firebase Firestoreを使ってユーザーのフォロー状態を管理するシンプルな機能を実装しています。toggleFollow関数でフォロー/フォロー解除を切り替え、その状態をUIに反映します。
プロフィール画面の実装
// FollowButtonというReactコンポーネントを定義しています。
// currentUserId: 現在ログインしているユーザーのID。
// targetUserId: フォロー対象のユーザーのID。
function FollowButton({ currentUserId, targetUserId }) {
// フォロー状態を保持するためのstate。
// trueなら「フォロー中」、falseなら「未フォロー」。
const [isFollowing, setIsFollowing] = useState(false);
// データ読み込み中かどうかを示すstate。
// trueの間はボタンを読み込み状態にします。
const [loading, setLoading] = useState(true);
// フォロー状態を取得
// コンポーネントのマウント(初回表示)や、依存変数が変更されたときに実行される処理。
useEffect(() => {
// フォロー状態を確認する非同期関数を定義。
const fetchFollowStatus = async () => {
// サーバーに対して、現在のフォロー状態を問い合わせるAPIを呼び出します
const response = await fetch(`/api/follow-status?follower_id=${currentUserId}&followed_id=${targetUserId}`);
// レスポンスをJSONとしてパース。
const data = await response.json();
// APIの結果に応じてフォロー状態を更新。
setIsFollowing(data.isFollowing);
// ローディング状態を解除。
setLoading(false);
};
// 両方のIDが揃っているときのみチェック処理を実行。
if (currentUserId && targetUserId) {
fetchFollowStatus();
}
// currentUserIdまたはtargetUserIdが変わったら再実行。
}, [currentUserId, targetUserId]);
// ボタンがクリックされたときに実行する関数。
const toggleFollow = async () => {
// ボタンを一時的に読み込み状態に。
setLoading(true);
// すでにフォローしているかをチェック。
if (isFollowing) {
// アンフォローするためにAPIにDELETEリクエストを送る。
await fetch(`/api/unfollow?follower_id=${currentUserId}&followed_id=${targetUserId}`, { method: 'DELETE' });
// フォローするためにAPIにPOSTリクエストを送信。
// JSON.stringifyでボディを文字列に変換。
} else {
// フォロー
await fetch(`/api/follow`, {
method: 'POST',
body: JSON.stringify({ follower_id: currentUserId, followed_id: targetUserId }),
});
}
// 状態をトグル(反転)して、画面表示を更新。
setIsFollowing(!isFollowing);
読み込み終了。
setLoading(false);
};
//下記return以降の処理
// 読み込み中は、押せないボタンを表示。
if (loading) return <button disabled>読み込み中...</button>;
// 状態に応じて「フォロー中」または「フォロー」のラベルを切り替えるボタンを表示。
return (
<button onClick={toggleFollow}>
{isFollowing ? 'フォロー中' : 'フォロー'}
</button>
);