// 로그인 상태인 경우의 댓글 등록창
<textarea
className="text-sm w-[98%] h-[60%] p-2 resize-none"
placeholder="댓글을 입력해주세요."
value={comment}
onChange={handleInputComment}
/>
// 로그아웃 상태인 경우
<textarea
readOnly={!isLoggedIn}
className="text-sm w-[98%] h-[60%] p-2 resize-none"
placeholder={
isLoggedIn ? "댓글을 입력해주세요." : "로그인이 필요합니다."
}
value={comment}
onChange={handleInputComment}
/>
문제 : 댓글 등록, 수정, 삭제 했을 때 댓글 리스트와 댓글 수를 동시에 최신화해야한다! 따라서 mutation했을때 무효화할 queryKey를 동시에 2개를 넣고 싶은데 1개씩 넣으면 잘 되던 무효화가 2개를 동시에 넣으니 둘 중 아무것도 무효화되지 않았다.
기존 코드
// 댓글 리스트 가져오기 useQuery
const {
data: commentsList,
isLoading: listIsLoading,
isError: listIsError,
} = useQuery({
queryKey: ["commentsList"],
queryFn: () => getCommentsList(drawingId),
});
// 댓글 개수 가져오기 useQuery
const {
data: commentsCount,
isLoading: countingIsLoading,
isError: countingIsError,
} = useQuery({
queryKey: ["commentsCounting"],
queryFn: () => getCommentsCount(drawingId),
});
// 댓글 등록 mutation
const { mutate: insertCommentMutation } = useMutation({
mutationFn: async (data: InsertingComment) => {
const { email, nickname, comment } = data;
await insertComment({ email, nickname, comment, drawingId });
},
onSuccess: () => {
queryClient.invalidateQueries({
// ⭐️⭐️ queryKey를 이렇게 2개를 동시에 넣으면 될 줄 알았는데 안됨..
queryKey: ["commentsList", "commentsCounting"],
});
},
});
⭐️ 해결 : queryKey의 prefix를 활용하자!
(참고 : tanstack-query 공식문서)
prefix
란?
- 쉽게 생각하면 컴포넌트에도 부모 컴포넌트, 자식 컴포넌트가 있듯이 queryKey의 부모격이 생기는 것과 유사하다.
- 체육, 수학, 영어는 '과목'으로 묶이듯이, 여러 queryKey들을 묶는 제목을 따로 만들어줘서
invalidateQueries
할 때queryKey
를 보다 더 자유자재로 활용할 수 있게 된다!- ex)
'과목'을 전부 다 무효화시켜줘
'과목' 중 '체육'만 무효화시켜줘
등등
prefix
queryKey를 만들어주고, 2개를 동시에 invalidateQueries
(쿼리키 무효화) 하고싶을 때 해당 prefix
만 불러주면 끝!!!// ✅ Comments.tsx - Palette Ground 프로젝트
// 댓글 리스트 가져오기
const {
data: commentsList,
isLoading: listIsLoading,
isError: listIsError,
} = useQuery({
// ⭐️⭐️ prefix - "comments", 뒤에는 해당 queryKey의 속성이라고 보면 됨
queryKey: ["comments", { type: "list" }],
// queryKey: ["comments", { page: 1 }], 이렇게 해도 됨
queryFn: () => getCommentsList(drawingId),
});
// 댓글 개수 가져오기
const {
data: commentsCount,
isLoading: countingIsLoading,
isError: countingIsError,
} = useQuery({
// ⭐️⭐️ 여기도 역시 prefix - "comments"
queryKey: ["comments", { type: "count" }],
// queryKey: ["comments", { page: 2 }], 이렇게 해도 됨
queryFn: () => getCommentsCount(drawingId),
});
// 댓글 등록 mutation
const { mutate: insertCommentMutation } = useMutation({
mutationFn: async (data: InsertingComment) => {
const { email, nickname, comment } = data;
await insertComment({ email, nickname, comment, drawingId });
},
onSuccess: () => {
queryClient.invalidateQueries({
// ⭐️⭐️ queryKey가 "comments"인 애들 무효화하기
// ( = prefix가 "comments"인 애들이 전부 무효화됨)
queryKey: ["comments"],
});
},
});
📝 요약 정리
queryKey: ["queryKey1", "queryKey2"]
로 하면 될 줄 알았더니 둘 다 무효화가 안됨prefix
를 사용하면 됨// useQuery에서
queryKey: ["comments", { type: list }]
queryKey: ["comments", { type: count }]
// mutation onSuccess에서
invalidateQueries({
queryKey: ["comments"]
})
// 앞에 "comments"가 붙은 queryKey들이 동시에 무효화됨
로직
방법 : .not("drawing_id", "eq", drawingId);
"eq"
: 비교 연산자(여기서는 "equals"
를 의미)drawing_id
필드의 값이 drawingId
와 같은 경우를 찾음not
이 붙어있으니까 같은애를 빼라는 뜻코드
// ✅ painter-api.ts
// posts테이블에서 일치하는 email의 그림 url들을 배열로 반환하기
export const getDrawingUrls = async (
email: string | null,
drawingId: number | undefined
) => {
const { data: urlArray, error } = await supabase
.from("posts")
.select("drawing_url")
.eq("painter_email", email)
.not("drawing_id", "eq", drawingId);
if (error) {
throw error;
}
const urls = urlArray.map((urlObj) => urlObj.drawing_url);
return urls;
};
// ✅ Comments.tsx - Palette Ground 프로젝트
// 쿼리가 완료된 후에 commentsList를 날짜순으로 정렬, 리스트 업데이트(setQueryData)
useEffect(() => {
if (commentsList && commentsList.length > 0) {
const sortedList = commentsList.sort((a, b) => {
const dateA = new Date(a.created_at);
const dateB = new Date(b.created_at);
return dateB.getTime() - dateA.getTime(); // 최신순으로 정렬
});
// 정렬된 리스트를 업데이트
queryClient.setQueryData(["comments", { type: "list" }], sortedList);
}
}, [commentsList, queryClient]);