조금조금 리액트, React-Query, queryKey?

Edwin·2023년 4월 21일
0

조금조금 REACT

목록 보기
25/31

조금조금 리액트, React-Query, queryKey?

이번 프로젝트가 벌써 3주차에 들어서고 있다. 내가 맡은 부분은 메인페이지와 아트그램 페이지이다. 여기서 아트그램이란 전시회에 대한 포스트를 기록하는 공간을 말한다. 그리고 오늘에서야 만나게 된 트러블 이슈가 있어서 기록하고자 한다. 결론은 "queryKey의 고유성에 대해서 확실하게 기억하자"다.

문제살펴보기

살펴볼 수 있듯이 답글(이하, 대댓글)에 대한 생성이 정상적으로 이뤄지는 것을 확인할 수 있다. 확인은 해당댓글의 답글보기(대댓글)에 표시된 숫자로 알 수 있다. 그러나 문제는 답글보기(대댓글)를 클릭하여, 들어가서 대댓글을 살펴보면, 먼저 호출된 대댓글이 아후 호출된 대댓글로 덮어씌어지는 문제가 있었다.

코드를 살펴보자, 비동기통신 - useQuery()

import { useQuery } from "@tanstack/react-query";
import { apis } from "../../../api/apis";
import { keys } from "../../../shared/queryKeys";

export const useGetReply = (artgramId, commentId) => {
  
  const {isLoading, isError, data} = useQuery({
    queryKey:keys.GET_ARTGRAMREPLY,
    queryFn: async() => {
      const response = await apis.get(`/artgram/${artgramId}/comments/${commentId}/reply`);
      return response.data.Reply
    },
    onSuccess: (data) =>{
      console.log("대댓글 조회성공");
    },
    onError: (e) => {
      console.log("대댓글 조회실패", e.message)
    }
  })
  return {isLoading, isError, data}
}

리액트 코드에 있어서 비동기통신은 react-query를 사용하였고, API로는 AXIOS를 사용하여 진행하였다. 내용은 공식문서에서 볼 수 있는 것과 같이 작성하였다. 통신이 이뤄지면 isLoading, isError, data가 반환되는 커스텀 훅이다.

이제 해당 커스텀 훅이 사용되는 컴포넌트를 살펴보자

function ArtgramDetailReply({artgramId, commentId}) {
  const { isLoading, isError, data: reply } = useGetReply(
    artgramId,
    commentId
  );

  return (
    <>
      {isLoading || isError 
        ? (<div>로딩 중 ...</div>) 
        : reply && reply.map(reply=> (
       	<CommentBox key={reply.commentId}>
          <CommentBoxProfileImg img={reply.profileImg} />
          <CommentBoxInnerText>{reply.comment}
          <div style={{display:"flex",gap:"8px"}}>
            <div>{timehandle(reply.createdAt)}</div>
          </div>
          </CommentBoxInnerText>
        </CommentBox>))}
    </>
  );
}

export default ArtgramDetailReply

1) 아트그램 페이지에서 map으로 생성된 ArtgramBox 컴포넌트

현재 컴포넌트만으로는 설명되지 않는 부분이 있기에 아래에서 추가적인 설명을 달고자 한다. 먼저 해당 컴포넌트는 아트그램 페이지에서 각각의 아트그램을 클릭했을 때부터 시작된다.

02) ArtgramBox 컴포넌트와 artgramId

서버에서 받아온 간추린 데이터에서 artgramId를 props로 받은 ArtgramBox는 개별 아트그램에 대한 상세요청을 서버에 보내고, 전달받은 data를 화면에 그린다. 이 역시 해당로직을 커스텀 훅으로 분리하여 담당시켰다. 단지 ArtgramBox에서 해 줄 작업은 전달받은 props를 서버전송을 위해 보내주는 일이다.

const [detailIsLoading, detailIsError, detailData] = useGetartgramDetail(artgramId);

03) ArtgramDetailComments와 artgramId

이후 해당 컴포넌트는 댓글을 그리기 위해서 자녀 컴포넌트로 artgramId를 보내주고, 해당 자녀컴포넌트인 ArtgramDetailComments는 마운트 되었을 때 댓글을 서버에 요청하고, 이를 화면에 그리도록 하였다.

04) ArtgramDeteilCommentsEdit와 ArtgramDetailReply

ArtgramDetailComments에서 생성된 반복되는 댓글들에 대한 개별 수정을 위해서 컴포넌트를 하나 더 분리하여 하위로 들어갔고, 해당 위치에서 읽기, 수정, 삭제 등을 비롯한 추가적인 동작을을 구성하였다. 그리고 그 추가적인 동작 가운데 하나가 ArtgramDetailReply 컴포넌트에서 실행되는 대댓글에 대한 입력과 조회인 것이다.

나의 접근

첫째, map으로 들어와서 생성된 컴포넌트이기에 각각의 컴포넌트가 고유하지 않을까?

get이 요청되는 상황을 분리하여 컴포넌트화하여, 각각의 상황에서만 요청이 발생되도록 나름의 최적화를 놓았다. 그런 상태였기에 ArtgramDetailReply에서 펼쳐진 여러 개의 댓글에 대한 요청이 서버로 보내졌을 때, 해당 요청이 이뤄질 것이라고 생각했다.

둘째, 2시간 동안 코드를 살펴보다 GPT.. 그러나.. 그는 도움이 되지 못했다..

무엇이 잘못되었을까를 고민하며 계속해서 코드를 산만한 코드를 정리하며, 로직에 대해서 살펴보았다. 아무리 생각해도 문제는 없어보였기 때문이다. 각각의 get 요청이 있을 때, 값이 아니라 전달받은 artgramId과 commentId를 출력해보아도, 각각의 댓글에 때한 대댓글의 Id들이 정상출력되었기 때문이다.

GPT는 ArtgramDetailReply 컴포넌트가 열려지는 조건인 ArtgramDeteilCommentsEdit의 const [showReply, setShowReply] = useState(false)를 지적했다.

{!showReply
          ? <div style={{display:"grid", gridTemplateColumns:"50px 1fr"}}>
            <div style={{borderTop: "1px solid black", margin: "6px 0", marginRight:"10px"}}></div>
            <div onClick={()=>setShowReply(pre=>!pre)}> 답글보기 ({comment.replyCount})</div>
          </div>
          : <ArtgramDetailReply artgramId={artgramId} commentId={comment.commentId}/>
        }

요청은 따로이나, 각각의 대댓글을 관장하는 useState이 전역에서 단일하게 사용되기 때문이라고 하였다. 그래서 단순한 false가 아니라, 이를 객체로 변환하여, 해당 댓글의 아이가 true이면 열리도록 설정하라는 것이었다. 그러나 이 역시도 해결되지는 않았다. 댓글에 대한 대댓글을 고유한 값을 가진 것이 아니라 차후의 값으로 전부 통일되는 문제는 계속되었다.

이어서 GPT는 캐싱의 문제일 수 있다며, useQuery의 cacheTime()을 0으로 하여 이전에 기억하고 있던 데이터를 기억하지 말고 새롭게 받아온 값으로 언제나 초청시킬 것을 요구했다.

queryFn: async() => {
      const response = await apis.get(`/artgram/${artgramId}/comments/${commentId}/reply`);
      return response.data.Reply
    },
    cacheTime: 0,

그러나 결과적 자리에서 돌아보면, 저 부분도 문제를 위한 해결이 될 수는 없었다.

셋째, 그렇게 또다시 흐른 1시간...
코드를 차분히 살펴보고 React-Query에 대한 지식을 돌아보게 되었다. 누가 말해준 것도 아니고, 찾아봐도 자료는 나오지 않고, 라이브러리를 사용했다는 이야기만 나왔지만, 번뜩!!! React-Query에 대한 지식으로 나아가게 되었다.

React-Query에서 queryKey는 매우 중요한 역할을 하는데, GET요청에 해당되 useQuery()를 사용할 때, queryKey는 쿼리를 고유하게 식별하는 데 사용되며, 일반적으로 쿼리에 필요한 매개변수 및 매개변수의 값을 포함하는 녀석이다.

문제는 queryKey

 const {isLoading, isError, data} = useQuery({
    queryKey:keys.GET_ARTGRAMREPLY,
    queryFn: async() => {
      const response = await apis.get(`/artgram/${artgramId}/comments/${commentId}/reply`);
      return response.data.Reply
    }...})

해당 댓글들(복수)에 대한 요청이 있지만, queryKey:keys.GET_ARTGRAMREPLY가 하나라는 부분에서 발생한 문제가 위의 사례였다. 생각해보면 정말 단순했다. 각각의 대댓글이 get요청을 한다면 각각의 get 요청에 대한 queryKey가 고유해야 한다는 것이다. 이는 tanstack 공식문서
에서도 확인해 볼 수 있는 부분이다. 나는 이것을 망각했던 것이다.

Query Keys
At its core, TanStack Query manages query caching for you based on query keys. Query keys have to be an Array at the top level, and can be as simple as an Array with a single string, or as complex as an array of many strings and nested objects. As long as the query key is serializable, and unique to the query's data, you can use it!

대댓글이 하나라면 get요청될 queryKey도 하나, 대댓글이 두개라면 get요청될 queryKey는 두개, 세개라면 세개, 네개라면 네개가 되어야 하는 것이다.

그래서 나는 아래의 쿼리키를 설정된 기본소스에 artgramId+commentId이 더해진 문자열을 생성했다.

 queryKey:[keys.GET_ARTGRAMREPLY+artgramId+commentId],

이를 통해서 각각의 대댓글은 고유한 상태가 유지될 수 있게 되었다. 그렇다면, 이제 해당 대댓글에 해당되는 내용이 삭제되거나, 입력될 때, 실행될 invalidateQueries을 설정해 주면 될 것이다. 물론 아래와 같은 방법도 있다.

queryClient.invalidateQueries((queryKey) => {
  return queryKey.includes(keys.GET_ARTGRAMREPLY);
});

keys.GET_ARTGRAMREPLY가 있는 모든 내용에 대해서 invalidateQueries을 실행해 주는 것이다. 사실이는 GPT가 추천한 방법인데 좋아보이지 않는다. 실행된 keys.GET_ARTGRAMREPLY+artgramId+commentId에 대한 invalidateQueries만 실행되면 되지 않겠는가? 이를 위해서 필자는 아래와 같이 POST와 DELETE가 실행되는 useMutation()에서 아래와 같이 기록하여 문제를 해결했다.

queryClient.invalidateQueries([keys.GET_ARTGRAMREPLY+artgramId+commentId]);

keys.GET_ARTGRAMREPLY+artgramId+commentId에 대한 부분이 변경되면, 해당 부분의 invalidateQueries만 실행될 것이다. 이를 통해서 위의 문제를 해결할 수 있었다.

profile
신학전공자의 개발자 도전기!!

0개의 댓글