개인프로젝트: VarGen(3.2)-useQuery 재호출 관리

윤뿔소·2023년 3월 16일
0

전편에서는 stale, fresh 상태와 맞추어 하나의 key의 상태에 따라 재호출을 막거나 설정하는 방법에 대해서 알아봤다. 또다른 문제를 직면했으니 그걸 보자

상황

전편처럼 잘 불러와지고, refetch 되지도 않는다. 하지면 다시 제출 버튼을 눌러서 호출을 눌렀을 때 fetching이 되지 않는다. 이게 어떻게 된 일일까?

그걸 살펴보자.

React-Query Devtools

리액트쿼리는 네트워크 호출 관련 상태 라이브러리라고도 불린다. 왜? '캐싱'이 가능하기 때문.

즉, 캐싱이 돼서 각 데이터가 key를 붙여 관리할 수 있다는 얘기다. 그 상태를 볼 수 있는 도구가 바로 ReactQueryDevtools다. 위 상황에서 어떤 것이 문제인지 파악할 수 없기에 라이브러리를 가져와 관찰해보자.

사용법

그냥 겁나 쉽다. react-query 라이브러리가 깔아져 있다면 그냥 불러오면된다. 어떻게?

import { ReactQueryDevtools } from "react-query/devtools";
...
export default function Home() {
  ...
  <ReactQueryDevtools />
  ...
}

아쉽게도 react-query 라이브러리 자체에 ReactQueryDevtools를 불러올 태그가 없어 따로 import를 해줘야한다. 저렇게 하고 표출하고 싶은 페이지에 삽입만 한다면
이렇게 이쁜 꽃이 렌더링되고,
꽃을 클릭하면 저렇게 useQuery 호출 관련 데이터를 볼 수 있게 된다!

내 데이터는 키가 prompt 자체로 돼있기에 저렇게 나오고, 그 값으로 resText가 저렇게 나오는 모습을 볼 수 있다.


다시 돌아와서 submit 버튼을 눌렀을 때 fetching이 되지 않는 이유는 키가 Create 10 login-related variable names with React라는 값을 가진 하나의 데이터가 존재하기 때문이다.
리액트 쿼리는 같은 데이터에 대한 여러번의 요청이 있을 시 중복을 제거한다는 특징이 있기에(그래서 fresh, stale이라는 상태를 둔 것) fresh 상태에서 임의로 재호출을 실행해도 되지 않는것.

그러면 어떻게 해야할까? 또 옵션을 사용해야지?

해결책

2편에서 봤던 queryClient를 가져와 체이닝된 메소드인 invalidateQueries()를 써야한다.

queryClient.invalidateQueries(*삭제할 쿼리*)

이렇게 말이다. 파라미터에 아무 것도 적어주지 않으면 모든 쿼리가 무효화된다.

즉, invalidateQueries를 refetch 하기 전에 먼저 캐싱된 쿼리들을 무효화하고 값을 비워주고, 그다음 요청을 보내게 하면 같은 키에 새로운 값이 와서 제출을 눌렀을 때의 무반응이 없어진 것!

근데 뭐임? 왜 안됨?

또다른 문제에 국면한 것이다.. 바로 똑같이 무반응이 일어난 것. 왜그러지? 싶었다. 거즘 2시간을 찾아보고 removeQueries도 써보고, 버튼이 아니라 다른 곳에 써보고 이것 저것 해본 결과 문제점을 찾았다.

바로 인스턴스를 2개 선언해서 다른 인스턴스에서 invalidateQueries을 실행해 지워주지 못해 무반응이 일어난 것.

2편에서 봤던 QueryClient를 생성자로 선언해 인스턴스 queryClient를 선언해주고 QueryClientProvider에 넣어줬었는데, invalidateQueries를 쓴답시고 또 QueryClient를 선언해 index.tsx, PromptInput.tsx 2개의 인스턴스를 생성했다.

그렇게 되면 index.tsx에서 그동안 쿼리들을 생성하고 그 난리를 피우고 있었는데, PromptInput.tsx에서 생성한 인스턴스의 쿼리를 지우니 index.tsx의 쿼리들에게 영향을 주겠는가?

오브젝트와 인스턴스의 관계, 생성 및 리액트 쿼리의 이해도에 대해 낮아 생긴 문제였다. 난 바보다!

바로 이사 때려버렸다.

index.tsx

import PromptInput from "@/components/PromptInput";
import Head from "next/head";

export default function Home() {
  return (
    <>
      <Head>
        <title>Vargen</title>
        <meta name="description" content="변수 생성 앱" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <PromptInput />
    </>
  );
}

index에는 차피 리액트쿼리를 안쓰고, 디자인 요소만 들어갈 것이니 PromptInput 컴포넌트만 남겨놓고 싹다 버렸다.

PromptInput.tsx

import { getChatResponse } from "@/pages/api/chatai";
import { useState } from "react";
import { QueryClient, useQuery, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

interface ChatResponseProps {
  prompt: string;
}

const queryClient = new QueryClient();

export default function PromptInput() {
  const [inputValue, setInputValue] = useState("");
  const [promptValue, setPromptValue] = useState("");

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    queryClient.invalidateQueries("chatResponse");
    if (inputValue !== "") {
      setPromptValue(inputValue);
    }
  };

  const ChatResponse = ({ prompt }: ChatResponseProps) => {
    const { data, isLoading } = useQuery(["chatResponse", prompt], () => getChatResponse(prompt), {
      staleTime: Infinity,
      cacheTime: Infinity,
    });

    if (isLoading) return <div>Loading...</div>;

    return <div>{data?.resText}</div>;
  };

  return (
    <QueryClientProvider client={queryClient}>
      <form onSubmit={handleSubmit}>
        <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
        <button type="submit">Submit</button>
      </form>
      {/* <ChatResponse prompt={`Create 10 게시판 related variable names with camelCase`} /> */}
      {promptValue && <ChatResponse prompt={promptValue} />}
      <ReactQueryDevtools />
    </QueryClientProvider>
  );
}

여기에 QueryClientProvider, queryClient, ReactQueryDevtools 등 리액트쿼리 관련 코드를 가져와서 했다.

좀 길어져서 아마도 input부분과 리액트쿼리 부분을 따로 나눌 것 같다. 만약 나뉘게 된다면 queryClient도 공유해야하기에 props로 내리는 방법을 강구해봐야겠다.

이제 나누고 실행 해보니 잘 된다! 이제 다음 시간엔 기능을 어떻게 사용해야 사람들이 잘 사용할 수 있는지에 대한 기획과 디자인도 한번 해보겠다.

profile
코뿔소처럼 저돌적으로

6개의 댓글

comment-user-thumbnail
2023년 3월 18일

리액트 쿼리 .. 어려워 보이네요 상황이랑 해결방안 잘보고 갑니다 나중에 쓰게 된다면 도움이 될거 같습니다

답글 달기
comment-user-thumbnail
2023년 3월 19일

리액트 쿼리 다시 공부하고 갑니다!! 그래도 역시 어렵네요 ...

답글 달기
comment-user-thumbnail
2023년 3월 19일

리액트 쿼리 어려운데 재밌어보여요.. 글이 재밌어서일까요? 잘 보고 갑니다!

답글 달기
comment-user-thumbnail
2023년 3월 19일

어렵네요... 나중에 리액트 쿼리 공부하면 이해할 수 있겠죠..? 🥲

답글 달기
comment-user-thumbnail
2023년 3월 19일

리액트 쿼리는 정말 유용한데 뿔소님은 정말 잘 활용하시는 듯 합니다.

답글 달기
comment-user-thumbnail
2023년 3월 20일

오늘도 봐도 모르겠는 어려운 태연님 블로그 ㅠㅠ

답글 달기