강의

멘토링

로드맵

Inflearn brand logo image

인프런 커뮤니티 질문&답변

장산님의 프로필 이미지
장산

작성한 질문수

[리뉴얼] React로 NodeBird SNS 만들기

게시글 좋아요

게시글 좋아요 부분 강의에서 하트색이 바로 변하지 않습니다

작성

·

540

0

에러는 없고 데이터에서도 like하고 unlike가 잘되는건 확인했는데 화면에서는 새로고침 해야지만 바뀌네요

그리고 좋아요하고 다시 눌러서 취소 할려는데 취소가 안되고 계속 중복으로 좋아요가 됩니다

스크린샷 2023-04-10 오후 3.39.08.png

const express = require("express");

const { Post, Image, Comment, User } = require("../models");
const { isLoggedIn } = require("./middlewares");

const router = express.Router();
router.post("/", isLoggedIn, async (req, res, next) => {
  try {
    const post = await Post.create({
      content: req.body.content,
      userId: req.user.id,
    });
    const fullPost = await Post.findOne({
      where: { id: post.id },
      include: [
        {
          model: Image,
        },
        {
          model: Comment,
          include: [
            {
              model: User, // 댓글 작성자
              attributes: ["id", "nickname"],
            },
          ],
        },
        {
          model: User, // 게시글 작성자
          attributes: ["id", "nickname"],
        },
        {
          model: User, // 좋아요 누른 사람
          as: "Likers",
          attributes: ["id"],
        },
      ],
    });
    res.status(201).json(fullPost);
  } catch (error) {
    console.error(error);
    next(error);
  }
});
router.post("/:postId/comment", isLoggedIn, async (req, res, next) => {
  try {
    const post = await Post.findOne({
      where: { id: req.params.postId },
    });
    if (!post) {
      return res.status(403).send("존재하지 않는 게시글입니다"); //return을 붙여줘야지 send하고 밑에 json이 동시에 실행안됨
    }
    const comment = await Comment.create({
      content: req.body.content,
      PostId: req.params.postId,
      userId: req.user.id,
    });
    const fullComment = await Comment.findOne({
      where: { id: comment.id },
      include: [
        {
          model: User,
          attributes: ["id", "nickname"],
        },
      ],
    });
    res.status(201).json(fullComment);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

router.patch("/:postId/like", isLoggedIn, async (req, res, next) => {
  try {
    const post = await Post.findOne({
      where: { id: req.params.postId },
    });
    if (!post) {
      return res.status(403).send("게시글이 존제하지 않습니다");
    }
    await post.addLikers(req.user.id);
    res.json({ PostId: post.id, userID: req.user.id });
  } catch (error) {
    console.error(error);
    next(error);
  }
});
router.delete("/:postId/like", isLoggedIn, async (req, res, next) => {
  try {
    const post = await Post.findOne({
      where: { id: req.params.postId },
    });
    if (!post) {
      return res.status(403).send("게시글이 존제하지 않습니다");
    }
    await post.removeLikers(req.user.id);
    res.json({ PostId: post.id, userID: req.user.id });
  } catch (error) {
    console.error(error);
    next(error);
  }
});

router.delete("/", (req, res) => {
  res.json({});
});

module.exports = router;

const express = require("express");

const { Post, Image, User, Comment } = require("../models");

const router = express.Router();

router.get("/", async (req, res, next) => {
  try {
    const posts = await Post.findAll({
      limit: 10,
      order: [
        ["createdAt", "DESC"],
        [Comment, "createdAt", "DESC"],
      ], //옵션  = ASC: 오름차순
      include: [
        {
          model: User,
          attributes: ["id", "nickname"], //아이디 ,닉네임만 가져오기
        },
        {
          model: Image,
        },
        {
          model: Comment,
          include: [
            {
              model: User,
              attributes: ["id", "nickname"],
            },
          ],
        },
        {
          model: User, // 좋아요 누른 사람
          as: "Likers",
          attributes: ["id"],
        },
      ],
    });
    res.status(200).json(posts);
  } catch (error) {
    console.error(error);
    next(error);
  }
});

module.exports = router;

import { Card, Button, Avatar, Popover, List, Comment } from "antd";
import {
  RetweetOutlined,
  HeartOutlined,
  MessageOutlined,
  HeartTwoTone,
  EllipsisOutlined,
} from "@ant-design/icons";
import { useDispatch, useSelector } from "react-redux";
import PropTypes from "prop-types";
import PostImages from "./PostImages";
import { useCallback, useEffect, useState } from "react";
import CommentForm from "./CommentForm";
import PostCardContent from "./PostCardContent";
import FollowButton from "./FollowButton";
import {
  REMOVE_POST_REQUEST,
  LIKE_POST_REQUEST,
  UNLIKE_POST_REQUEST,
} from "../reducers/post";

const PostCard = ({ post }) => {
  const dispatch = useDispatch();
  const { removePostLoading } = useSelector((state) => state.post);
  const [commentFormOpened, setCommentFormOpened] = useState(false);
  const id = useSelector((state) => state.user.me?.id);
  const onLike = useCallback(() => {
    if (!id) {
      return alert("로그인이 필요합니다.");
    }
    dispatch({
      type: LIKE_POST_REQUEST,
      data: post.id,
    });
  }, [id]);
  const onUnLike = useCallback(() => {
    if (!id) {
      return alert("로그인이 필요합니다.");
    }
    dispatch({
      type: UNLIKE_POST_REQUEST,
      data: post.id,
    });
  }, [id]);

  const onToggleComment = useCallback(() => {
    setCommentFormOpened((prev) => !prev);
  }, []);
  const onRemovePost = useCallback(() => {
    dispatch({
      type: REMOVE_POST_REQUEST,
      data: post.id,
    });
  }, []);
  const liked = post.Likers.find((v) => v.id === id);
  console.log("@@@", liked);
  return (
    <div style={{ marginBottom: 10 }}>
      <Card
        cover={post.Images[0] && <PostImages images={post.Images} />}
        actions={[
          <RetweetOutlined key="retweet" />,
          liked ? (
            <HeartTwoTone
              twoToneColor="#eb2f96"
              key="heart"
              onClick={onUnLike}
            />
          ) : (
            <HeartOutlined key="heart" onClick={onLike} />
          ),
          <MessageOutlined key="message" onClick={onToggleComment} />,
          <Popover
            key="more"
            content={
              <Button.Group>
                {id && post.user?.id === id ? (
                  <>
                    <Button>수정</Button>
                    <Button
                      type="danger"
                      onClick={onRemovePost}
                      loading={removePostLoading}
                    >
                      삭제
                    </Button>
                  </>
                ) : (
                  <Button>신고</Button>
                )}
              </Button.Group>
            }
          >
            <EllipsisOutlined />
          </Popover>,
        ]}
        extra={id && <FollowButton post={post} />}
      >
        <Card.Meta
          avatar={<Avatar>{post.user?.nickname[0]}</Avatar>}
          title={post.user?.nickname}
          description={<PostCardContent postData={post.content} />}
        />
      </Card>
      {commentFormOpened && (
        <div>
          <CommentForm post={post} />
          <List
            header={`${post.Comments.length}개의 댓글`}
            itemLayout="horizontal"
            dataSource={post.Comments}
            renderItem={(item) => (
              <li>
                <Comment
                  author={item.user?.nickname}
                  avatar={<Avatar>{item.user?.nickname[0]}</Avatar>}
                  content={item.content}
                />
              </li>
            )}
          />
        </div>
      )}
    </div>
  );
};

PostCard.propTypes = {
  post: PropTypes.shape({
    id: PropTypes.number,
    user: PropTypes.object,
    content: PropTypes.string,
    createdAt: PropTypes.string,
    Comment: PropTypes.arrayOf(PropTypes.object),
    Images: PropTypes.arrayOf(PropTypes.object),
    Likers: PropTypes.arrayOf(PropTypes.object),
  }).isRequired,
};

export default PostCard;

답변 2

1

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

빨간색이 바로 안 들어오는 것은 리덕스 리듀서 문제입니다. 리듀서에서 해당하는 게시물을 찾아서 Likers에 내 아이디를 제대로 추가했는지 확인해보셔야 합니다.

장산님의 프로필 이미지
장산
질문자

case LIKE_POST_REQUEST:
        draft.likePostLoading = true;
        draft.likePostDone = false;
        draft.likePostError = null;
        break;
      case LIKE_POST_SUCCESS: {
        const post = draft.mainPosts.find((v) => v.id === action.data.PostId);
        post.Likers.push({ id: action.data.userId });
        draft.likePostLoading = false;
        draft.likePostDone = true;
        break;
      }
      case LIKE_POST_FAILURE:
        draft.likePostLoading = false;
        draft.likePostError = action.error;
        break;
      case UNLIKE_POST_REQUEST:
        draft.unlikePostLoading = true;
        draft.unlikePostDone = false;
        draft.unlikePostError = null;
        break;
      case UNLIKE_POST_SUCCESS: {
        const post = draft.mainPosts.find((v) => v.id === action.data.PostId);
        post.Likers = post.Likers.filter((v) => v.id !== action.data.userId);
        draft.unlikePostLoading = false;
        draft.unlikePostDone = true;
        break;
      }
      case UNLIKE_POST_FAILURE:
        draft.unlikePostLoading = false;
        draft.unlikePostError = action.error;
        break;

리듀서에서는 제대로 들어가는거같은데

스크린샷 2023-04-10 오후 8.15.36.png바로 반영이 안되는거같네요...

현재 좋아요랑 좋아요취소랑 바로 반영이 안되고 새로고침해야지 반영이됩니다 ㅠㅠ 그리고 좋아요 버튼 두번누르면 좋아요 되었다가 바로 취소가 되어야하는데 중복으로 좋아요가 됩니다 ㅠㅠ

어디가 문제인지 잘모르겠습니다 ㅠㅠ

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

리듀서가 문제라서 반영이 안되는 겁니다. action 탭도 클릭해서 값들 제대로 있는지 확인하세요.

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

const post = draft.mainPosts.find((v) => v.id === action.data.PostId); post.Likers = post.Likers.filter((v) => v.id !== action.data.userId); draft.unlikePostLoading = false; draft.unlikePostDone = true;

이게 문제인거니까 각 요소들 다 확인을 해야죠

장산님의 프로필 이미지
장산
질문자

후 겨우 찾았습니다.

routes - post.js에 있는 res.json보니까 userID로 되어있고 reducer에서는 userId로 되어있어서 안되는거였습니다. 둘다 일치시켜주니까 작동되네요

제로초님 힌트아니였으면 눈치채지도 못했겠네요 ㅠㅠ감사합니다.요즘 대소문자때문에 많이 힘드네요 ㅠㅠ

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

나중에 타입스크립트로 꼭 넘어가세요. 그러면 오타를 낼 일이 많이 줄어듭니다.

0

장산님의 프로필 이미지
장산
질문자

삭제된 글입니다

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

입력이 된것 같은데요??

const liked = post.Likers.find((v) => v.id === id);

여기서 id랑 post.Likers를 개별적으로 콘솔로그해보세요.

장산님의 프로필 이미지
장산

작성한 질문수

질문하기