Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/server/src/controllers/v1/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ router.get('/:id/comments', verifyToken, async (req: Request, res: Response) =>
...comment.toObject(),
isAuthor: comment.get('userId')?.equals(req.user._id),
isLiked: comment.get('likes')?.includes(req.user._id),
likeCount: comment.get('likes')?.length || 0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

새로운 likeCount 필드가 응답에 잘 추가되었습니다. 다만, 이 로직은 apps/server/src/models/comments.tstoJSON 메서드에 정의된 로직과 중복됩니다. 작성자님께서도 PR 설명에 언급해주셨듯이, 현재는 toObject()를 사용하기 때문에 모델의 toJSON에 정의된 likeCount가 포함되지 않는 문제가 있습니다.

장기적인 유지보수성을 위해, 모델 레이어에서 데이터 가공을 처리하는 것이 좋습니다. 한 가지 방법은 Mongoose의 가상(virtual) 프로퍼티를 사용하는 것입니다. 모델 스키마에 likeCount를 가상 프로퍼티로 정의하고 toObject: { virtuals: true } 옵션을 활성화하면, 컨트롤러에서 별도의 로직 없이 comment.toObject()를 통해 일관되게 likeCount 값을 얻을 수 있습니다. 이는 코드 중복을 줄이고 역할을 명확히 분리하는 데 도움이 될 것입니다. 추후 리팩토링 시 고려해보시면 좋겠습니다.

}))
res.status(200).json({
comments: modifiedDocs,
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/api/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,13 @@ export async function deleteEventCommentById(request: IDeleteCommentRequest): Pr
}
return
}

export async function toggleEventCommentLike(eventId: string, commentId: string): Promise<void> {
const res = await fetch(`${SERVER_URL}/v1/events/${eventId}/comments/${commentId}/likes`, {
method: 'POST',
})
if (!res.ok) {
throw await res.json()
}
return
}
30 changes: 27 additions & 3 deletions apps/web/src/components/comment/comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { IComment, IDeleteCommentRequest } from '@fienmee/types'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'react-toastify'

import { deleteEventCommentById } from '@/api/event'
import { deleteEventCommentById, toggleEventCommentLike } from '@/api/event'
import CommentOption from '@/components/comment/commentOption'
import CommentUpdateField from '@/components/comment/commentUpdateField'
import { UnlikeIcon } from '@/components/icon'
import { LikeIcon, UnlikeIcon } from '@/components/icon'
import LoadingOverlay from '@/components/comment/commentLoadingOverlay'

interface Props {
Expand Down Expand Up @@ -36,6 +36,21 @@ export function EventComment({ comment }: Props) {
})
}

const likeMutation = useMutation({
mutationFn: ({ eventId, commentId }: { eventId: string; commentId: string }) => toggleEventCommentLike(eventId, commentId),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ['comments', comment.eventId] })
},
onError: () => {
toast.error(<span>좋아요 처리에 실패했습니다.</span>)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋아요가 제대로 반영되지 않았어요. 다시 시도해주세요.

이런것 처럼 문구를 바꾸는게 좋을 것 같아요.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변경 했습니다!

},
})
Comment on lines +39 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

좋아요 기능이 useMutation을 사용해 잘 구현되었습니다. 사용자 경험을 한 단계 더 향상시키기 위해 낙관적 업데이트(Optimistic Updates) 적용을 고려해볼 수 있습니다. 현재는 onSuccess에서 invalidateQueries를 호출하여 데이터를 다시 가져오는 방식인데, 이 경우 네트워크 지연에 따라 UI 업데이트가 늦어질 수 있습니다.

낙관적 업데이트를 사용하면, 서버 응답을 기다리지 않고 UI를 먼저 변경하여 사용자에게 즉각적인 피드백을 줄 수 있습니다. 만약 서버 요청이 실패하면 원래 상태로 되돌립니다.

react-queryonMutateonError 콜백을 사용하여 이를 구현할 수 있습니다. onMutate에서 queryClient.setQueryData를 사용해 캐시를 직접 업데이트하고, onError에서 롤백하는 방식입니다. 이 방식은 사용자에게 더 빠르고 부드러운 경험을 제공할 수 있습니다.


const handleToggleLike = () => {
if (likeMutation.isPending) return
likeMutation.mutate({ eventId: comment.eventId, commentId: comment._id })
}

return (
<div className="flex flex-col gap-6 py-2 relative">
{deleteMutation.isPending && <LoadingOverlay />}
Expand All @@ -49,7 +64,16 @@ export function EventComment({ comment }: Props) {
)}
</div>
<div className="flex flex-row justify-center items-center gap-2">
<UnlikeIcon width="1.5rem" height="1.25rem" />
<button
aria-label="toggle comment like"
onClick={handleToggleLike}
disabled={likeMutation.isPending}
className="disabled:opacity-50"
type="button"
>
{comment.isLiked ? <LikeIcon width="1.5rem" height="1.25rem" /> : <UnlikeIcon width="1.5rem" height="1.25rem" />}
</button>
<span className="text-sm">{comment.likeCount}</span>
{comment.isAuthor && (
<CommentOption onEdit={() => setIsEdit(true)} onDelete={handleDelete} isDeleteDisabled={deleteMutation.isPending} />
)}
Expand Down