추가 - 좋아요 기능
본문 바로가기
항해 중/5주차 리액트 심화반

추가 - 좋아요 기능

by 은돌1113 2021. 12. 3.

1. firebase database -> post collection -> like_cnt, like_list(해당 게시물의 좋아요를 누른 사람 목록) 추가


2. 좋아요 뷰 만들기

 

post_list에서 좋아요를 눌렀을 때 색상 변하는 방법을 설명하기 전에

 

좋아요 기능을 넣기 전의 코드에서는 PostList.js에서 Post 컴포넌트에 onClick을 사용해서 /postDetail로 보냈는 데

(즉, 게시물 목록에서 게시물을 누를 경우 상세 페이지로 이동 시켰는 데)

그럴 경우 좋아요를 누를 수 없어서 게시물 목록(PostList.js)에 있는 onClick을 Post.js에서 contents와 image에만 적용 되도록 바꿨다.

 

방법 1)

- state에 좋아요 유무를 boolean 값을 넣는다.

- state에 좋아요 유무에 따라 string 값을 넣는다.

- styled-components를 사용해서 Like를 만들고, styles를 props로 넘긴다.

- Like에서는 props로 받아온 color와 삼항 조건식을 사용해서 좋아요를 누른 상태라면 pink를 주고, 좋아요를 해제한 상태라면 gray를 부여한다.

// Post.js

import React from "react";
import { Grid, Image, Text, Button } from "../elements";
import { history } from "../redux/configureStore";
import styled from 'styled-components'

const Post = (props) => {

  const [like, setLike] = React.useState(false); // ***
  const [color, setColor] = React.useState('unLike'); // ***

  const styles = {color : color}; // ***

  const likeClick = () => { // ***
    if(like){
      setLike(false)
      setColor('unLike');
      // 좋아요를 누르면 firebase, redux에 like_cnt + 1
    }else{
      setLike(true)
      setColor('like')
      // 좋아요 해제하면 firebase, redux에 like_cnt - 1
    }
  }

  return (
    <React.Fragment>
      <Grid>
        <Grid is_flex padding="16px">
          <Grid is_flex width="auto">
            <Image shape="circle" src={props.user_profile}></Image>
            <Text bold>{props.user_info.user_name}</Text>
          </Grid>
          <Grid is_flex width="auto"> // *******
            <Text>{props.insert_dt}</Text>
            {props.is_me && (
              <Button
                width="auto"
                padding="4px"
                margin="4px"
                _onClick={() => {
                  history.push(`/postWrite/${props.id}`);
                }}
              >
                수정
              </Button>
            )}
          </Grid>
        </Grid>
        <Grid
          _onClick={() => {
            history.push(`postDetail/${props.id}`);
          }}
        >
          <Grid padding="16px">
            <Text>{props.contents}</Text>
          </Grid>
          <Grid>
            <Image shape="rectangle" src={props.image_url}></Image>
          </Grid>
        </Grid>
        <Grid padding="16px" is_flex>
          <Text bold>댓글 {props.comment_cnt}개</Text>
          {/* 좋아요 유무 */}
          <Like {...styles} onClick={likeClick}>♥</Like> // ***
        </Grid>
      </Grid>
    </React.Fragment>
  );
};

Post.defaultProps = {
  user_info: {
    user_name: "오새봄",
    user_profile: "https://i.ytimg.com/vi/Ct1Pp_4FEIY/maxresdefault.jpg",
  },
  image_url: "https://i.ytimg.com/vi/Ct1Pp_4FEIY/maxresdefault.jpg",
  contents: "오새봄이다. 찬양해라",
  comment_cnt: 10,
  insert_dt: "2021-11-29 19:09:00",
};

const Like = styled.div` // ***
  font-size : 30px;
  color : ${(props) => (props.color === 'like')? 'pink' : 'gray'};
`

export default Post;

 

방법 2)

- state에 좋아요 유무를 boolean 값을 넣는다.

- 삼항 조건식을 사용해서 true이면 꽉 찬 하트를 보여주고 false이면 빈 하트를 보여준다.

// Post.js

import React from "react";
import { Grid, Image, Text, Button } from "../elements";
import { history } from "../redux/configureStore";
import styled from 'styled-components'

const Post = (props) => {
  console.log(props);

  const [like, setLike] = React.useState(true); // ***

  const likeClick = () => { // ***
    if(like){
      setLike(false)
      // 좋아요를 누르면 firebase, redux에 like_cnt + 1
    }else{
      setLike(true)
      // 좋아요 해제하면 firebase, redux에 like_cnt - 1
    }
  }

  return (
    <React.Fragment>
      <Grid>
        <Grid is_flex padding="16px">
          <Grid is_flex width="auto">
            <Image shape="circle" src={props.user_profile}></Image>
            <Text bold>{props.user_info.user_name}</Text>
          </Grid>
          <Grid is_flex width="auto">
            <Text>{props.insert_dt}</Text>
            {props.is_me && (
              <Button
                width="auto"
                padding="4px"
                margin="4px"
                _onClick={() => {
                  history.push(`/postWrite/${props.id}`);
                }}
              >
                수정
              </Button>
            )}
          </Grid>
        </Grid>
        <Grid
          _onClick={() => {
            history.push(`postDetail/${props.id}`); // *******
          }}
        >
          <Grid padding="16px">
            <Text>{props.contents}</Text>
          </Grid>
          <Grid>
            <Image shape="rectangle" src={props.image_url}></Image>
          </Grid>
        </Grid>
        <Grid padding="16px" is_flex>
          <Text bold>댓글 {props.comment_cnt}개</Text>
          {/* 좋아요 유무 */}
          {like ? ( // ***
            <Like size="20px" bold onClick={likeClick}>
              ♥
            </Like>
          ) : (
            <Like size="20px" bold onClick={likeClick}>
              ♡
            </Like>
          )}
        </Grid>
      </Grid>
    </React.Fragment>
  );
};

Post.defaultProps = {
  user_info: {
    user_name: "오새봄",
    user_profile: "https://i.ytimg.com/vi/Ct1Pp_4FEIY/maxresdefault.jpg",
  },
  image_url: "https://i.ytimg.com/vi/Ct1Pp_4FEIY/maxresdefault.jpg",
  contents: "오새봄이다. 찬양해라",
  comment_cnt: 10,
  insert_dt: "2021-11-29 19:09:00",
};

const Like = styled.div`
  font-size : 30px;
`

export default Post;

3. 좋아요 기능 모듈

import { createAction, handleActions } from "redux-actions";
import { produce } from "immer";
import { firestore } from "../../shared/firebase";
import firebase from "firebase/compat/app";

// 액션 타입
const MINUS_LIKE = "MINUS_LIKE";
const ADD_LIKE = "ADD_LIKE";

// 액션 생성 함수
const addLike = createAction(ADD_LIKE, (post_id, like_cnt) => ({
  post_id,
  like_cnt,
}));

const minusLike = createAction(MINUS_LIKE, (post_id, like_cnt) => ({
  post_id,
  like_cnt,
}));

// 초기값
const initialState = {
  post_id: null,
  like_cnt: 0,
};

// 미들웨어
const addLikeFB = (post_id = null, like_cnt, like_list) => {
  return function (dispatch, getState, { history }) {
    const postDB = firestore.collection("post");
    const user_id = getState().user.user.uid; // 사용자 uid

    const increment = firebase.firestore.FieldValue.increment(1);
    // 인자에 들어간 값만큼 현재 값에서 추가 해준다.

    postDB
      .doc(post_id)
      .update({ like_cnt: increment, like_list: [...like_list, user_id] })
      .then((docs) => {
        window.alert("좋아요를 누르셨습니다.");
        window.location.reload();
      });
  };
};

const minusLikeFB = (post_id = null, like_cnt, like_list) => {
  return function (dispatch, getState, { history }) {
    const postDB = firestore.collection("post");
    const user_id = getState().user.user.uid; // 사용자 uid

    const increment = firebase.firestore.FieldValue.increment(-1);
    // 인자에 들어간 값만큼 현재 값에서 추가 해준다.

    const new_like_list = like_list.filter((l) => {
      return l !== user_id;
    });

    postDB
      .doc(post_id)
      .update({ like_cnt: increment, like_list: new_like_list })
      .then((docs) => {
        window.alert("좋아요를 취소하셨습니다.");
        window.location.reload();
      });
  };
};

// 리듀서
export default handleActions(
  {
    [ADD_LIKE]: (state, action) => {
      produce(state, (draft) => {});
    },
    [MINUS_LIKE]: (state, action) => {
      produce(state, (draft) => {});
    },
  },
  initialState
);

// 액션 생성 함수 export
const actionCreators = {
  addLikeFB,
  minusLikeFB,
};

export { actionCreators };

4. Post.js 최종

// Post.js

import React from "react";
import { Grid, Image, Text, Button } from "../elements";
import { history } from "../redux/configureStore";
import styled from "styled-components";
import Permit from "../shared/Permit";
import { useSelector, useDispatch } from "react-redux";
import { actionCreators as likeActions } from "../redux/modules/like";

const Post = (props) => {
  const user_id = useSelector((state) => state.user.user?.uid);
  const post = useSelector((state) => state.post.list);

  const [like, setLike] = React.useState(false);
  const [color, setColor] = React.useState("unLike");
  const styles = { color: color };

  const dispatch = useDispatch();

  // 좋아요 유무에 따라서 새로고침이 되어도 그대로 반영 되게 하는 코드
  React.useEffect(()=>{
    console.log(post)
    const _post = post.filter((p) => {
      // 게시물 정보에서 좋아요를 누른 사람의 목록을 가져온다.
      return p.id === props.id;
    })[0].like_list;
  
    if (_post) {
      _post.forEach((p) => {
        if (p === user_id) {
          setLike(true);
          setColor('like');
        }
      });
    }
  })

  const likeClick = (props) => {
    const post_id = props.id; // 게시물 정보
    const like_cnt = props.like_cnt; // 좋아요 갯수
    const post_user_id = props.user_info.user_id; // 게시물 작성자
    const like_list = props.like_list; // 좋아요 누른 사람들 아이디

    // 게시물 작성자인 지 체크
    if (post_user_id === user_id) {
      window.alert("작성자의 게시물에는 좋아요를 누르실 수 없습니다.");
      return;
    }
    // else if(like_list.includes(user_id)){ // 이게 진짜 필요할까??
    //   window.alert("이미 좋아요를 눌렀습니다.")
    //   return;
    // }

    if (like) {
      // 안좋아요
      setLike(false);
      setColor("unLike");
      // 좋아요 해제하면 firebase, redux에 like_cnt - 1
      dispatch(likeActions.minusLikeFB(post_id, like_cnt, like_list));
    } else {
      // 좋아요
      setLike(true);
      setColor("like");
      // 좋아요를 누르면 firebase, redux에 like_cnt + 1
      dispatch(likeActions.addLikeFB(post_id, like_cnt, like_list));
    }
  };

  return (
    <React.Fragment>
      <Grid>
        <Grid is_flex padding="16px">
          <Grid is_flex width="auto">
            <Image shape="circle" src={props.user_profile}></Image>
            <Text bold>{props.user_info.user_name}</Text>
          </Grid>
          <Grid is_flex width="auto">
            <Text>{props.insert_dt}</Text>
            {props.is_me && (
              <Button
                width="auto"
                padding="4px"
                margin="4px"
                _onClick={() => {
                  history.push(`/postWrite/${props.id}`);
                }}
              >
                수정
              </Button>
            )}
          </Grid>
        </Grid>
        <Grid
          _onClick={() => {
            history.push(`postDetail/${props.id}`);
          }}
        >
          <Grid padding="16px">
            <Text>{props.contents}</Text>
          </Grid>
          <Grid>
            <Image shape="rectangle" src={props.image_url}></Image>
          </Grid>
        </Grid>
        <Grid padding="16px" is_flex>
          <Text bold>댓글 {props.comment_cnt}개</Text>
          <Text bold>좋아요 {props.like_cnt}개</Text>
          <Permit>
            {/* 좋아요 유무 */}
            <Like
              {...styles}
              onClick={() => {
                likeClick(props);
              }}
            >
              ♥
            </Like>
          </Permit>
        </Grid>
      </Grid>
    </React.Fragment>
  );
};

Post.defaultProps = {
  user_info: {
    user_name: "오새봄",
    user_profile: "https://i.ytimg.com/vi/Ct1Pp_4FEIY/maxresdefault.jpg",
  },
  image_url: "https://i.ytimg.com/vi/Ct1Pp_4FEIY/maxresdefault.jpg",
  contents: "오새봄이다. 찬양해라",
  comment_cnt: 10,
  insert_dt: "2021-11-29 19:09:00",
};

const Like = styled.div`
  // ***
  font-size: 30px;
  color: ${(props) => (props.color === "like" ? "pink" : "gray")};
`;

export default Post;

댓글