❓발생한 이슈/고민
댓글 추가 및 리스트만 표시되는 상태에서 댓글 수정, 삭제 기능을 맡았다.
해결해야 할 과제는
- '가나다라마바사'가 아닌 유저 닉네임이 들어가도록 수정
- 본인이 쓴 댓글만 '...' 버튼 보이게 하기
- 댓글 삭제 기능 추가
- 댓글 수정 기능 추가
- 댓글작성 시간 추가
- isError 설정
- ...버튼 클릭시 왼쪽으로 드롭다운 창 표시 (드롭다운 창 내부는 수정과 삭제)"
- 작성 댓글 유저 닉네임 옆에 프로필 이미지 보이도록 수정
💡해결과정
'가나다라마바사'가 아닌 유저 닉네임이 들어가도록 수정
const { user } = useUserStore();
export type CommentType = {
// ...
writerEmail: string;
}
// ...
const onSubmitCommentHandler = (e: React.FormEvent<HTMLFormElement>) => {
const newComment: CommentType = {
// ...
writerEmail: user?.email || '',
}
}
로그인 중인 사용자의 이메일이 코멘트 작성자의 이름으로 사용된다. 사용자가 로그인하지 않았을 경우, 작성자 이름은 "익명"으로 설정된다.
본인이 쓴 댓글만 '...' 버튼 보이게 하기
CommentItem 컴포넌트에서 isCommentAuthor 변수를 사용하여 본인이 작성한 댓글에 대해서만 MoreOutlined('...') 버튼이 표시되도록 코드를 수정했다.
const { user } = useUserStore();
const isCommentAuthor = comment.writerNikName === user?.displayName;
// ...
{isCommentAuthor && (
<Button icon={<MoreOutlined />} />
)}
** 원래는 유저의 닉네임과 댓글 작성자의 닉네임을 비교해서 일치하는 요소를 찾는 방식으로 했었는데,
-> 이메일로 댓글 작성자와 현재 로그인한 유저를 비교하는 구문으로 수정했다.
중복 닉네임 검증 기능이 구현되어 있다고 해도, 기존 작성자가 닉네임을 수정하고 다른 유저가 기존 유저의 닉네임을 사용해 버리면 결론적으로 남이 작성한 댓글의 수정/삭제 권한을 받을 수 있는 것이 아닌가? 라고 생각했기 때문.
유저 닉네임이 아닌 이메일을 기준으로 댓글 작성자를 확인하는 것이 보다 안전하게 사용 가능하다고 판단했다.
댓글 삭제 기능 추가
코드 변경 사항
댓글 상태를 관리하기 위해 useState를 사용하며 초기 상태를 data로 설정했다.
// api/comment.ts
export const getComments = async ({ queryKey }: { queryKey: string[] }) => {
const [_, crsId] = queryKey;
const res = await axios.get<CommentType[]>(`${COMMENT_URL}?crsId=${crsId}`);
return res.data;
};
export const deleteComment = async (commentId: string) => {
await axios.delete(`${COMMENT_URL}/${commentId}`);
};
// CommentList.tsx
const { data, isError, isLoading, error } = useQuery(['comments', crsId as string], getComments);
useEffect(() => {
if (data) {
setComments(data);
}
}, [data]);
data가 변경될 때마다 댓글 상태를 업데이트하도록 useEffect로 설정했다.
삭제 된 후 상태를 업데이트하고 쿼리를 다시 가져오도록 onDeleteComment 함수를 생성 후
CommentList 컴포넌트에서 선택한 댓글을 삭제할 수 있는 로직을 추가.
const onDeleteComment = async (commentId: string) => {
try {
await deleteComment(commentId);
setComments((prevComments) => prevComments.filter((comment) => comment.id !== commentId));
queryClient.invalidateQueries(['comments']);
} catch (error) {
console.error('댓글 삭제 중 에러 발생:', error);
}
};
이 로직은 CommentItem 컴포넌트에서 사용할 수 있다.
return (
<St.CommentListContainer>
{comments.map((comment) => {
return (
<CommentItem
key={comment.id}
comment={comment}
onDeleteComment={onDeleteComment}
onEditComment={onEditComment}
/>
);
})}
</St.CommentListContainer>
);
CommentItem 에서 props로 받아와서 아래의 handleDelete함수를 button에 onClick으로 넣어서 사용하면 된다.
const CommentItem = ({ comment, onDeleteComment, onEditComment }: CommentItemType) => {
// ...
const handleDelete = () => {
const isConfirm = window.confirm('정말 삭제하시겠습니까?');
if (isConfirm) {
return onDeleteComment(comment.id);
}
};
댓글 수정 기능 추가
댓글 수정 로직은 상태 관리와 API 호출 두 가지 관점에서 작동하며 다음과 같이 진행했다.
1. 댓글 수정 api 생성
// api/comments.ts
export const modifyComment = async (commentId: string, updatedComment: CommentType) => {
await axios.patch(`${COMMENT_URL}/${commentId}`, updatedComment);
};
2. 댓글 수정 버튼 클릭: 사용자가 댓글 수정 버튼을 클릭하면 해당 댓글의 편집 모드로 전환된다.
// CommentItem.tsx
const handleEdit = () => {
setIsEditing(true);
};javascript
3. 댓글 수정을 완료하면 handleSubmit 핸들러를 통해 변경된 컨텐츠가 저장된다.
const handleSubmit = (values: { edited: string }) => {
setIsEditing(false);
const updatedComment = { ...comment, content: values.edited };
onEditComment(comment.id, updatedComment);
};
이 때, 부모 컴포넌트인 CommentList의 onEditComment 함수에 댓글 ID와 업데이트된 댓글이 전달된다.
4. onEditComment 함수에서는 댓글을 수정하는 API 호출을 수행한다.
이렇게 하면 서버의 데이터가 업데이트되며, 응답에 따라 클라이언트의 상태도 갱신된다.
const onEditComment = async (commentId: string, updatedComment: CommentType) => {
try {
await modifyComment(commentId, updatedComment);
setComments((prevComments) =>
prevComments.map((comment) => (comment.id === commentId ? updatedComment : comment)),
);
queryClient.invalidateQueries(['comments']);
} catch (error) {
console.error('댓글 수정 중 에러 발생:', error);
}
};
5. 수정된 댓글을 setComments 함수를 사용하여 현재 댓글 목록에 반영한다.
이를 통해 수정된 댓글이 상태에 반영되며 화면에 표시된다.
6. 최종적으로 queryClient.invalidateQueries(['comments']);를 호출하여 React Query 캐시를 무효화하고 새로운 데이터를 불러오도록 하여, 다른 사용자가 추가한 새로운 댓글이나 이전 수정 사항이 반영된다.
이렇게 댓글 수정 로직은 수정 버튼 클릭, 수정 내용 저장, 수정된 댓글 목록 반영 및 캐시 무효화와 같은 과정을 거쳐 사용자가 원하는 수정 사항을 저장하고 다시 보여줄 수 있다.
'...'버튼 클릭시 왼쪽으로 드롭다운 창 표시 (드롭다운 아이템은 수정과 삭제)
const dropdownMenu = (
<Menu>
<Menu.Item key="1" onClick={handleEdit}>
수정
</Menu.Item>
<Menu.Item key="2" onClick={handleDelete}>
삭제
</Menu.Item>
</Menu>
);
//드롭다운 선언 후
return (
// ...
{isCommentAuthor && (
<St.Dropdown overlay={dropdownMenu} trigger={['click']} placement="bottomRight">
<Button icon={<MoreOutlined />} />
</St.Dropdown>
)}
)
// return에서 사용
댓글작성 시간 보이기
CommentType 타입 선언을 수정하여 timestamp 속성을 추가
export type CommentType = {
id: string;
crsId: string;
writerNikName: string;
content: string;
timestamp: number;
};
newComment 객체를 생성할 때 timestamp 속성에 현재 시간을 저장
const newComment: CommentType = {
id: nanoid(),
crsId: crsId as string,
writerNikName: user?.displayName || '익명',
content: comment,
timestamp: Date.now(),
};
댓글을 렌더링하는 CommentItem 컴포넌트에서 작성 시간을 toLocaleString()으로 변환하여 화면에 출력
<St.CommentTime>{new Date(comment.timestamp).toLocaleString()}</St.CommentTime>
댓글 작성 시간이 Date 객체로 저장되며, 출력하기 전에 toLocaleString()을 사용해 적절한 형식으로 변환된다.
예) new Date(data.timestamp).toLocaleString() => 2022. 3. 24. 오전 9:00:00
isError 설정
isError 구조분해 해서 선언은 되어있는데 사용하고 있는 곳이 없어서 에러 상태 로직 구현
const { data, isError, isLoading, error } = useQuery(['comments', crsId as string], getComments);
// ...
if (isError && error) {
return <div>댓글 목록을 불러오는 중 오류가 발생했습니다: {(error as AxiosError).message}</div>;
}
작성 댓글 유저 닉네임 옆에 프로필 이미지 보이도록 추가
CommentType에 프로필 이미지 URL을 저장하는 속성을 추가
export type CommentType = {
id: string;
crsId: string;
writerNikName: string;
writerEmail: string;
writerPhotoURL: string; // 추가
content: string;
timestamp: number;
};
newComment 객체를 생성할 때 writerPhotoURL 필드를 저장하도록 한다.
const newComment: CommentType = {
id: nanoid(),
crsId: crsId as string,
writerNikName: user?.displayName || '익명',
writerEmail: user?.email || '',
writerPhotoURL: user?.photoURL || '', // 추가
content: comment,
timestamp: Date.now(),
};
CommentItem.tsx의 return에서 antd 라이브러리 avatar를 이용해서 적용
<Avatar src={comment.writerPhotoURL} />
완성한 댓글
수정도 잘된다
🧐궁금점과 부족한 내용
antd 드롭다운 위치때문에 css 수정 하려고 했는데
import { Dropdown as AntdDropdown } from 'antd';
import { styled } from 'styled-components';
export const CustomDropdown = styled(AntdDropdown)`
.ant-dropdown {
position: absolute !important;
top: 0 !important;
left: calc(-100% - 32px) !important;
}
.ant-dropdown .ant-dropdown-menu {
background-color: red !important;
}
`;
!important로 우선 설정 해줘도 적용이 안된다..
왜...? 도통 이해할 수가 없다.
📋레퍼런스
'내일배움캠프 > Today I Learned' 카테고리의 다른 글
[TIL 2023.08.16] 최종 프로젝트 시작! (0) | 2023.08.16 |
---|---|
[TIL 2023.08.09] 회원가입 프로필 이미지 저장 오류 (0) | 2023.08.10 |
[TIL 2023.08.08] 심화 프로젝트 둘러보미 / 회원가입 만들기 (0) | 2023.08.08 |
[TIL 2023.08.07] 심화 프로젝트 둘러보미 (0) | 2023.08.07 |
[TIL 2023.08.04] Next.js 미니 영화 앱 만들기 (0) | 2023.08.04 |