Firestore를 이용한 follow기능 추가

신동원·2021년 7월 31일
2

Firebase와 React

목록 보기
2/2

SNS 핵심 기능이라고 할 수 있는 follow기능.

user collection에 followings, followers를 모두 저장하지는 않는다.
왜냐하면 문서의 크기가 제한되어 있기 때문이다.

user collection에는 count 정보만 저장하고
following이라는 새로운 collection을 만들어, 그곳에 팔로워 정보를 저장한다.

로그인한 유저 정보를 auth에서 불러오고
지금 보고있는 profile의 유저를 follow하는 상황이다.

followUser 함수

const db = firebase.firestore();

export async function followUser(profile) {
  const user = firebase.auth().currentUser; //currentuser가 profile을 follow함
  try {
    //currentuser의 following collection, following하는 대상에 profile을 추가
    await db
      .collection('following')
      .document(user.uid)
      .collection('userFollowing')
      .doc(profile.id)
      .set({
        displayName: profile.displayName,
        photoURL: profile.photoURL,
        uid: profile.id,
      });
    //proilfe(대상)의 following collection, follower에 user를 추가
    await db
      .collection('following')
      .document(profile.id)
      .collection('userFollowers')
      .doc(user.uid)
      .set({
        displayName: user.displayName,
        photoURL: user.photoURL,
        uid: user.uid,
      });
    //user가 팔로잉 하는 사람 숫자를 추가
    await db
      .collection('users')
      .doc(user.uid)
      .update({
        followingCount: firebase.firestore.FieldValue.increment(1),
      });
    //profile(팔로잉 대상)의 팔로워 사람 숫자를 추가
    return await db
      .collection('users')
      .doc(profile.id)
      .update({
        followerCount: firebase.firestore.FieldValue.increment(1),
      });
  } catch (error) {
    throw error;
  }
}

주의할 점은 user.uid와 profile.id를 헷갈리는 경우.
그 경우 firebase Requested entity was not found 라는 error가 출력될 것이다.
auth로 받아온 user에게는 uid로 id에 접근해야 한다.

followUser 컴포넌트 구현

그 다음 컴포넌트에서는 아래와 같이 구현했다.

export default function ProfileHeader({ profile, isCurrentUser }) {
  //주석1
  const [loading, setLoading] = useState(false);
  //주석2
  async function handleFollowUser() {
    setLoading(true);
    try {
      await followUser(profile);
    } catch (error) {
      toast.error(error.message);
      //주석3
    } finally {
      setLoading(false);
    }
  }
  
  return (
    //...(생략)
  )
}

주석1:
isCurrentUser를 통해 지금 프로필을 보고 있는 것이 auth 상 user인지 여부를 체크.
내가 내 프로필을 보는 경우 팔로우 버튼 대신 프로필 관리 버튼이 뜬다.

주석2:
loading indicator를 통해 팔로우 작업이 처리되는 도중에는 follow 버튼이 disable된다.

주석3
무슨 이유에서건 팔로우가 제대로 처리되지 않으면 user에게 toast 메시지를 보여주게 되어 있긴 한데
개발 코드를 toast 메시지로 보여주는 것은 아주 나쁜 사용자 경험이므로 수정할 예정.

unfollowUser 함수

팔로우 기능을 구현했으니 물론 unfollow 기능도 구현해야 할 것이다.


export async function unfollowUser(profile) {
  const user = firebase.auth().currentUser;

  try {
    await db
      .collection('following')
      .doc(user.uid)
      .collection('userFollowing')
      .doc(profile.id)
      .delete();
    await db
      .collection('following')
      .doc(profile.id)
      .collection('userFollowers')
      .doc(user.uid)
      .delete();

    await db
      .collection('users')
      .doc(user.uid)
      .update({
        followingCount: firebase.firestore.FieldValue.increment(-1),
        //decrement가 따로 없기 때문에 increment(-1) 처리를 해주면 된다.
      });
    return await db
      .collection('users')
      .doc(profile.id)
      .update({
        followerCount: firebase.firestore.FieldValue.increment(-1),
      });
  } catch (error) {
    throw error;
  }
}

batch 활용

만약에 follower 기능이 중간에 막힌다면 어떨까?
예를 들면 아래 코드 첫 번째, 즉 내가 팔로우 하는 사람 명단에는 상대방이 추가되었는데
중간에 뭔가 문제가 생기고 error를 throw해 그 아래가 하나도 실행이 안 된다면 어떨까?

export async function followUser(profile) {
  const user = firebase.auth().currentUser;
  try {
    await db
      .collection('following')
      .document(user.uid)
      .collection('userFollowing')
      .doc(profile.id)
      .set({
        displayName: profile.displayName,
        photoURL: profile.photoURL,
        uid: profile.id,
      });
   // <<<<<<여기서 뭔가 문제가 터지는 것이다!!!!>>>>>
    await db
      .collection('following')
      .document(profile.id)
      .collection('userFollowers')
      .doc(user.uid)
      .set({
        displayName: user.displayName,
        photoURL: user.photoURL,
        uid: user.uid,
      });
    await db
      .collection('users')
      .doc(user.uid)
      .update({
        followingCount: firebase.firestore.FieldValue.increment(1),
      });
    //profile(팔로잉 대상)의 팔로워 사람 숫자를 추가
    return await db
      .collection('users')
      .doc(profile.id)
      .update({
        followerCount: firebase.firestore.FieldValue.increment(1),
      });
  } catch (error) {
    throw error;
  }
}

이 때 이용하는 것이 firestore의 batch 기능이다. 아래와 같이 사용한다.

const batch = db.batch();

try {
  batch.set(
   db.collection('following').doc(user.uid)
     .collection('userFollowing').doc(profile.id),
    {
       displayName: profile.displayName,
       photoURL: profile.photoURL,
       uid: profile.id,
    }
  );

  batch.set(
    db.collection('following').doc(profile.id)
      .collection('userFollowers').doc(user.uid),
    {
      displayName: user.displayName,
      photoURL: user.photoURL,
      uid: user.uid,
    }
  );

  batch.update(db.collection('users').doc(user.uid), {
    followingCount: firebase.firestore.FieldValue.increment(1),
  });
  batch.update(db.collection('users').doc(profile.id), {
    followerCount: firebase.firestore.FieldValue.increment(1),
  });
}

return await batch.commit();
profile
리액트를 좋아하는 프론트엔드 개발자

0개의 댓글