0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ChatGPTを使用したアプリ開発記【フォロー・フォロワー機能の実装】

Last updated at Posted at 2025-05-11

結論

ユーザー情報がFireStoreから取得できず、実装できませんでした。
Firebase error : failed to get document because the client is offline

スクリーンショット 2025-05-11 13.19.12.png

Firestoreのデータ設計

/users/{userId} {
  uid: "abc123",
  displayName: "たろう",
  followers: ["uid1", "uid2"],   // 自分をフォローしている人
  following: ["uid5", "uid7"]    // 自分がフォローしている人
}

フォローボタン機能の実装

followvutton.js
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;
  1. 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に反映します。

プロフィール画面の実装

profile.js

  // 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>
  );

  
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?