본문 바로가기
개발 이야기/직접 해보기

[React Query] useQuery로 검색 기능을 만들던 중 마주친 문제

by thedev 2023. 5. 15.

 

useQuery로 검색 기능 만들기

 


 

# 문제 상황

 

 검색 기능을 구현하기 위해 useEffect와 if 조건문을 사용하였다. input 태그로 검색어(searchText)를 입력하면 isSearching 변수가 true가 되고, isSearching가 true일 때 백엔드에 데이터를 요청하는 함수가 실행된다. 이를 구현하기 위해 사용한 코드는 다음과 같다.

 

const [submitCheck, setSubmitCheck] = useState(true);

 const getItems = async (page: number) => {
    try {
      const response = await fetch(`/api/items?page=${page}`);
      const result = await response.json();
      return result;
    } catch (error) {
      console.log(error);
      return null;
    }
 };
 
  const searchItems = async (page: number, name: string) => {
    try {
      const response = await fetch(`/api/search?page=${page}&name=${name}`);
      const result = await response.json();
      return result;
    } catch (error) {
      console.log(error);
      return null;
    }
 };
  
 useEffect(() => {
    if (isSearching) {
      searchItems(page, searchText).then((res) => setItemList(res));
    } else if (!isSearching) {
      getItems(page).then((res) => setItemList(res));
    }
  }, [page]);

  useEffect(() => {
    if (isSearching) {
      searchItems(page, searchText).then((res) => setItemList(res));
    }
 }, [submitCheck]);
 
 // 생략
 
 return (
   <form onSubmit={() => setSubmitCheck(!submitCheck)}>
     <input />
   </form>
 )

 

 submitCheck라는 변수는 form 태그의 submit event를 체크하기 위해 생성하였다. form 태그에서 submit event가 발생할 때마다 setSubmitCheck(!submitCheck)가 되도록 하여, submit이 발생하는지를 확인했다.

 

 그런데 이렇게 코드를 작성하고 나니, submitCheck라는 변수를 생성한 게 좀 많이 비효율적으로 보였다. submit을 따로 확인하는 변수를 만들지 않고도 searchText 변수의 변동을 확인하고, 알아서 새로 데이터를 fetch하는 방법은 없을까? 라고 고민하던 중 React Query가 생각나서 바로 사용해보았다.

 

 

# React Query 도입

 

if (isSearching) {
    useQuery(
      ["items", page, searchText],
      async () =>
        (
          await fetch(`/api/search?page=${page}&searchText=${searchText}`)
        ).json(),
      {
        onSuccess: (resposne) => {
          setItemList(resposne);
        },
        onError: (error) => {
          console.log(error);
        },
      }
    );
  } else {
    useQuery(
      ["items", page],
      async () => (await fetch(`/api/items?page=${page}`)).json(),
      {
        onSuccess: (resposne) => {
          setItemList(resposne);
        },
        onError: (error) => {
          console.log(error);
        },
      }
    );
  }

 

 isSearching 변수가 true일 때는 검색 결과를 요청하는 url을 fetch하고, false일 때는 검색이 아닌 일반 items을 요청하는 url을 fetch한다. 이렇게 React Query를 사용하면 변수의 변화를 감지하는 또 다른 변수를 생성하지 않아도, 변수의 변화를 알아서 감지하고 조건에 따라 원하는 url을 fetch 할 수 있다.

 

그러나!! 이렇게 작성하면 로컬 환경에서는 무리 없이 잘 작동하지만, 배포를 하면

 

React Hook "useQuery" is called conditionally. React Hooks must be called in the exact same order in every component render.

 

라는 에러가 발생한다.

 

 이유가 무엇인가 하니, 조건문 안에서 useQuery라는 Hook을 호출하는 것은 React의 규칙에 어긋나기 때문에 에러가 발생하는 것이라고 한다. (근데 그럼 로컬에서는 왜 오류가 안 났던 거야...😥) useQuery를 조건에 따라 다르게 사용하고 싶은데 조건문을 사용할 수 없다니... 고민하다가 해결 방법을 찾았다.

 

 

# 최종 수정 코드

 

 const queryKey = isSearching ? ["items", page, searchText] : ["items", page];
 
 const fetchURL = isSearching
    ? `/api/search?page=${page}&searchText=${searchText}`
    : `/api/items?page=${page}`;

 useQuery(queryKey, () => axios.get(fetchURL), {
    onSuccess: (response) => {
      setItemList(response.data);
    },
    onError: (error) => {
      console.log(error);
    },
 });

 

 바로, key와 url 값을 변수로 만들어서 변수에 조건문 걸기!! 코드가 너무나 간결해졌다🥳 useEffect 안에 if문으로 조건을 작성하고, url을 fetch하는 함수를 만들어서 가져다 사용하고... 라는 길고 긴 코드를 사용하다가 React Query를 사용하니 확실히 코드가 간결하고 가독성 높아지는 것을 확인할 수 있었다. 추가로 axios까지 사용하여 fetch 코드를 더 간결하게 바꾸었다.

 


 

 useQuery는 예전에 사용해본 적이 있어서 이미 쓰는 방법을 잘 알고 있다고 생각했는데... 아니었다. 이미 알고 있는 기술도 완전히 다 알고 있다고 생각하지 말고, 다시 사용해보며 더 잘 적용할 수 있는 방법을 찾아야겠다🧐