좋아요 기능

순9·2023년 11월 22일
0

리액트 게시판

목록 보기
40/54

구상

  1. page 컴포넌트에서 좋아요 버튼 추가
  2. 좋아요 버튼 클릭시 Thunk 활용 하여 파이어베이스에
    좋아요 관련 데이터 추가
  3. 좋아요 수, 좋아요 선택/해제

1차

파이어베이스에서 데이터 경로
최상위 컬렉션> 최상위 도큐멘트> 상위 컬렉션> 상위 도큐멘트
여기에 하위 컬렉션을 추가 해서 좋아요 관련 데이터를 관리 하려고 함

Page컴포넌트
import

import { useSelector, useDispatch } from "react-redux";
import userPageLike from "../../redux/thunks/boardPageLikeThunks";

return

 const handleGoodClick = async () => {
    await dispatch(userPageLike({ like: true }) as any); 
  };
return(
..코드생략
 <div>
    <button onClick={handleGoodClick}>좋아요</button>
 </div>
);

Thunk userPageLike

interface LikeType {
  like: boolean;
}

const userPageLike = createAsyncThunk(
  "userPage/likeClick",
  async (data: LikeType, thunkAPI) => {
    try {
      //사용자uid 정보 가져오기
      const auth = getAuth();
      const currentUser = auth.currentUser;
      const userId = currentUser?.uid;

      //최상위 컬렉션
      const userCollection = collection(firestore, "users");
      //최상위 도큐멘트
      const userDocRef = doc(userCollection, "userData");
      //최상위 컬렉션> 최상위도큐멘트>userId이름을 가진 하위 컬렉션
      const subCollectionRef = collection(userDocRef, userId as string);
      //하위 컬렌션이 가진 모든 하위 도큐멘트 출력 getDocs사용
      const specificDoc = await getDocs(subCollectionRef);

      //하위 도큐멘트 리스트
      for (const [index, docI] of specificDoc.docs.entries()) {
        const data = docI.data(); //하위 도큐멘트 데이터
        console.log(` ${data}`);
      
       //하위 도큐멘트
        const docRef = doc(subCollectionRef, dataIndex);

        //하위 컬렉션
        const lastCallectionRef = collection(docRef, "like");
        
        //데이터 추가
        //await addDoc(lastCallectionRef, {
            //like: true,
           // numClick: 3,
         // });
      }

    //반환 하는 값
      const responseDataLike: LikeType = {
        like: true,
      };

      return responseDataLike;
    } catch (error) {
      // 에러 처리
      console.error("Error in userPageLike thunk:", error);
      throw error;
    }
  }
);

export default userPageLike;

uid 이름으로 되어 있는 하위 컬렉션의 모든 하위 도큐멘트 가져 오도록 설정

문제점
어떤 페이지로 가도 모든 하위 도큐멘트들이 다 출력 됨
조건문을 주고 싶었는데 비교 할 대상이 없어서 계속 모든게 출력 됨☹

해결 방안을 생각 해봄
페이지의 id가 각 페이지 별로 있고 유일함 문젠 상태관리에 저장되어 있음 Thnuk에서 상태관리에 저장 되어 있는 페이지의id를 불러 올 수 있가 있나?

thunkAPI 활용
return

const state: any = thunkAPI.getState();
const boardSelectedId = state.board.selectedBoardId;

thunkAPI.getState(); 모든 상태들을 다 가져온다
가져온 상태에서 내가 가져오고 싶은 상태를 지정 한다

2차

사용자가 방문한 페이지의 id 와 하위 도큐멘트 데이터의 id를 비교 하여
일치하는 하위 도큐멘트에 하위 컬렉션 추가 되어 데이터가 추가 되는 것이 확인 됨

문제점
a라는 사용자의 페이지에 b라는 사용자가 좋아요를 클릭 하면 데이터가 추가가 안되고 a자기 자신의 페이지에 좋아요 데이터가 추가가 됨
현재 로그인한 사용자의 컬렉션 내부의 데이터만을 대상으로 좋아요 추가됨
데이터의 경로를 userUid로 했던 것이 문제 였던거 같다

코드 수정
Thunk userPageLike

const strId = data.boardId.toString();
const userCollection = collection(firestore, "users");
const userDocRef = doc(userCollection, "userData");
const subCollectionRef = collection(userDocRef, strId);


for (const [index, docI] of specificDoc.docs.entries()) {
        const data = docI.data();
        console.log(` ${data}`);
        const dataId = data.did;
        const dataIndex = data.index;
     
        //하위 도큐멘트
        const docRef = doc(subCollectionRef, dataIndex);

        //하위 컬렉션
        const lastCallectionRef = collection(docRef, "like");

        if (boardSelectedId === dataId) {
          await addDoc(lastCallectionRef, {
            like: true,
          });
        } else {
          console.log("틀렸어요");
        }
      }

컬렉션 명은 string만 가능 해서 string으로 변경 함
하위 컬렉션 명을 boardId로 변경 함

해결 방안
b사용자도 a사용자 페이지에 좋아요 표시를 할 수 있도록
데이터 경로 수정해서 가능 하도록 함 하위 like 컬렉션 추가 되는 것 확인

3차

문제 사항
사용자들의 중복을 막으려 했으나 어떻게 해야할지 고민..
Firebase Firestore에서 고유한 값(unique value)을 가진 필드를 직접적으로 제어하는 내장 기능은 제공되지 않는 다고 함

현재까지의 상황
자기 자신의 글 좋아요 클릭 불가 다른 사용 자들은 좋아요 클릭 가능
이제 좋아요 클릭한 수 계산해서 노출 하면 될듯

Page컴포넌트

 const handleGoodClick = async () => {
    await dispatch(
      userPageLike({
        boardId: boardSelectedId,
        boardPage: liked,
      }) as any
    );
  };

userPageLike가 받는 데이터 형식

boardPageLikedSlice

interface LikeType {
  like: boolean;
  userUid: string;
}

interface MyStateLike {
  boardPage: Record<number, LikeType[]>;
}

const initialState: MyStateLike = {
  boardPage: {},
};


const boardPageLikedSlice = createSlice({
  name: "pageLikes",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(userPageLike.pending, (state, action) => {
        console.log(`비동기 좋아요 요청 중`);
      })
      .addCase(userPageLike.fulfilled, (state, action) => {
        console.log(`비동기 좋아요 요청 성공`);
        const { boardId, boardPage: pageLike } = action.payload;
      })
      .addCase(userPageLike.rejected, (state, action) => {
        console.log(`비동기 좋아요 요청 실패`);
      });
  },
});

export default boardPageLikedSlice.reducer;

Record<number, LikeType[]>: 이 부분은 boardPage가 숫자 형태의 키와 LikeType 배열을 값으로 가지는 객체임을 나타낸다

Thunk userPageLike

interface LikeType {
  like: boolean;
  userUid: string;
}
const userPageLike = createAsyncThunk<
  { boardId: number; boardPage: LikeType },
  { boardId: number; boardPage: LikeType }
>("userPage/likeClick", async (data, thunkAPI) => {
	...코드 생략
    
   for (const [index, docI] of specificDoc.docs.entries()) {
      const data = docI.data();
      const dataId = data.did;
      const dataIndex = data.index;
      const dataUserId = data.userUid;
      //하위 도큐멘트
      const docRef = doc(subCollectionRef, dataIndex);

      //하위 컬렉션
      const lastCallectionRef = collection(docRef, "like");

      if (boardSelectedId === dataId && userId !== dataUserId) {
        await addDoc(lastCallectionRef, {
          like: true,
          userUid: userId,
        });
      } else {
        console.log("달라요------------------");
      }
    }

//반환 값
    const responseDataLike = {
      boardId: boardSelectedId,
      boardPage: {
        like: true,
        userUid: userId as string,
      },
    };

    return responseDataLike;
}

📢주의 사항
반환할때의 key이름 동일 하게 할것

4차

page 컴포넌트에서 좋아요 수 출력하기
파이어베이스에서 실시간 데이터 출력하기

const dataListPage = async () => {
    //컬렉션
    const userCollection = collection(firestore, "users");
    //도큐멘트
    const userDocRef = doc(userCollection, "userData");
    //컬렉션>도큐멘트>userId이름을 가진 하위 컬렉션
    const subCollectionRef = collection(userDocRef, boardSelectedId.toString());
    //하위 컬렌션이 가진 모든 하위 도큐멘트 출력 getDocs사용
    const specificDoc = await getDocs(subCollectionRef);

    specificDoc.docs.map(async (iData) => {
      const data = iData.data();
      const dataUserId = data.index;
      const docRef = doc(subCollectionRef, dataUserId);

      const lastCallectionRef = collection(docRef, "like");
      const underSpecificDoc = await getDocs(lastCallectionRef);

      const underDbLength = underSpecificDoc.size;

      onSnapshot() 
      setUnderDb(underDbLength);
      
    });
  };

5차 최종

사용자별 좋아요 선택/해제, 좋아요 수 출력 완료
아이콘 활용

모든 데이터관리를 상태관리, 파베에도 넣야 하는줄 알았는데 아니었다
상태관리는 전역라이브러리로 모든 페이지에서 사용할 데이터라고 한다 그래서 파이어베이스에만 데이터를 관리 하도록 했다

Page컴포넌트

import userPageLike from "../../redux/thunks/boardPageLikeThunks";
import FavoriteIcon from "@mui/icons-material/Favorite";
import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
import { firebaseApp, firestore } from "../../firebase";
import { collection, getDocs, doc, onSnapshot } from "firebase/firestore";
import userPageLikeOverturn from "../../redux/thunks/boardPageLikeOverturnThunk";


 const handleGoodClick = async () => {
    //컬렉션
    const userCollection = collection(firestore, "users");
    //도큐멘트
    const userDocRef = doc(userCollection, "userData");
    //컬렉션>도큐멘트>userId이름을 가진 하위 컬렉션
    const subCollectionRef = collection(userDocRef, boardSelectedId.toString());
    //하위 컬렌션이 가진 모든 하위 도큐멘트 출력 getDocs사용
    const specificDoc = await getDocs(subCollectionRef);

//userId 담을 빈배열
    let userUids: string[] = [];
    //하위 컬렉션 아래에 하위 도쿠멘트 가져오기
    specificDoc.docs.map(async (iData) => {
      const data = iData.data();
      const dataUserId = data.index;
      const docRef = doc(subCollectionRef, dataUserId);

//가져온 하위 도큐멘트에 하위 컬렉션 추가
      const lastCallectionRef = collection(docRef, "like");

      const underSpecificDoc = await getDocs(lastCallectionRef);
     
     //작성했던 빈배열에 uidVar가 있는지 확인
      if (!userUids.includes(uidVar)) {
        userUids.push(uidVar);


        const liked = {
          like: true,
          userUid: uidVar as string,
        };

        await dispatch(
          userPageLike({
            boardId: boardSelectedId,
            boardPage: liked,
          }) as any
        );
        setLikeClick(true);

// 가져온 하위컬렉션에서 하위 도큐멘트 가져 오기
//로그인한 사용자의 uid가 있으면 데이터 삭제
        underSpecificDoc.docs.map(async (und) => {
          const underd = und.data();
          if (underd.userUid === uidVar) {
            underd.like = false;

            const liked = {
              like: underd.like,
              userUid: uidVar as string,
            };

            await dispatch(
              userPageLikeOverturn({
                boardId: boardSelectedId,
                boardPage: liked,
              }) as any
            );
            setLikeClick(false);
            
          }
        });
      }
    });
  };
  
  
  
  return(
  //likeClick state에 맞게 클래스 이름이 들어 간 것으로 변경됨
  <div className="likeBtnBox">
          <button className="likeBtn" onClick={handleGoodClick}>
            {!likeClick ? (
              <div className="unSelectLike">
                <FavoriteBorderIcon></FavoriteBorderIcon>
              </div>
            ) : (
              <div className="selectLike">
                <FavoriteIcon></FavoriteIcon>
              </div>
            )}
          </button>
          <span>좋아요 수 {underDb}</span>
        </div>)
profile
1. 사용법 익히기 2. 원리가 뭔지 찾아보기 3. 원리를 공부하기

0개의 댓글