Post

tanstack Query - useQuery

useQuery

https://tanstack.com/query/latest/docs/framework/react/reference/useQuery

1. 소개

useQuery 훅은 서버 데이터를 가져오기 위해 사용되는 훅입니다. 별도의 상태나 이펙트를 작성하지 않아도 됩니다. 또한 데이터 요청에 대한 로직들을 한 곳에 작성할 수 있기 때문에 코드를 깔끔하게 작성할 수 있습니다. queryKey와 queryFn을 필수로 입력해야 합니다.

2. useQuery

1
2
3
4
5
const { data, error, status, ...returns } = useQuery({
  queryKey,
  queryFn,
  ...options
});

3. useQuery 옵션

#queryKey

  • 필수
  • 쿼리에 사용할 queryKey
  • queryKey가 변경되면 쿼리가 자동으로 업데이트 됨

#queryFn

  • 필수
  • 쿼리가 데이터를 요청하는데 사용하는 함수

#enabled 옵션

  • 쿼리를 자동으로 실행시킬지 여부 설정
  • false로 설정하면 자동으로 실행되지 않음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Todos = () => {
  const { data, refetch } = useQuery({
    queryKey: ["todos", { status, page }],
    queryFn: fetchTodoList,
    enabled: false
  });

  return (
    <div>
      {data.map((todo) => (
        <Todo todo={todo} />
      ))}
    </div>
  );
};

#select 옵션

  • 서버에서 가져온 데이터를 변환하거나 선택하는데 사용
  • 반환된 데이터에는 영향을 주지만, 쿼리 캐시에 저장되는 내용에는 영향을 주지 않음

가져온 데이터 중에, 할일이 완료되지 않은 것만 선택

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Todos = () => {
  const { data, refetch } = useQuery({
    queryKey: ["todos", { status, page }],
    queryFn: fetchTodoList,
    select: (value: any) => value.filter((todo: Todo) => !todo.isDone)
  });

  return (
    <div>
      {data.map((todo) => (
        <Todo todo={todo} />
      ))}
    </div>
  );
};

4. useQuery 반환값

#satus

  • 쿼리의 상태
  • pending: 캐시된 데이터가 없고 쿼리 시도가 아직 완료되지 않은 상태
  • error: 오류가 발생한 상태
  • success: 오류없이 응답을 성공적으로 수신한 상태

#isPending

  • status.pending과 같은 의미
  • 캐시가 없고, 쿼리 시도가 아직 완료되지 않아 오류중인 상태를 의미

#data

  • 쿼리를 요청하고 받아온 응답 값
  • 기본값은 undefined

#error

  • 오류가 발생한 경우 반환되는 오류 객체
  • 기본값은 null

#isLoading

  • 첫번째 요청이 진행 중일 때의 상태
  • isPending과 isFetching과 동일한 의미

#refetch

  • 버튼을 클릭하는 것과 같은 액션이 발생할 때 데이터를 가져올 때
  • 데이터가 업데이트되어 새롭게 데이터를 가져와야할 때 사용
1
2
3
4
5
6
7
8
9
10
11
function Todos() {
  const { data, refetch } = useQuery({
    queryKey: ["todos", { status, page }],
    queryFn: fetchTodoList
  });
  return (
    <div>
      <button onClick={() => refetch()}>refetch</button>
    </div>
  );
}

5. 같이 사용하면 좋은 옵션들

#enabled 옵션과 select옵션

초기에 todo 데이터를 가져오지 않고, 버튼을 클릭하면 refetch 함수를 통해 useQuery를 실행하여 가져온 데이터 중에 할일이 완료되지 않은 것들만 선택하여 사용

  • enabled를 false로 설정하여, 초기에 쿼리를 실행되지 않도록 설정
  • select 옵션을 통해 할일이 완료되지 않은 것들만 선택
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const { data, refetch } = useQuery({
  queryKey: ["todos", { status, page }],
  queryFn: fetchTodoList,
  enabled: false, // 쿼리를 초기에 실행하지 않음
  select: (value) => value.filter((todo) => !todo.isDone) // 가져온 데이터 중에 할일이 완료되지 않은 것만 선택
});

return (
  <div>
    {data.map((todo) => (
      <Todo todo={todo} />
    ))}
    <button onClick={() => refetch()}>다시 불러오기</button>
  </div>
);

6. useState,useEffect를 사용했을 때 vs useQuery 사용했을 떄

useQuery를 사용하면 useState,useEffect를 사용했을 때보다 적은 양의 코드를 작성하기 때문에 보다 깔끔한 코드를 작성할 수 있습니다.

다음은 params의 내용을 바탕으로 서버에 데이터를 요청하는 로직이 작성된 useBooks 훅입니다.

useState,useEffect를 사용했을 때

1. 상태와 이팩트 분리

  • useState를 사용하여 상태를 관리
  • useEffect를 사용하여 상태 변화에 따라 특정 동작을 수행

2. 수동 데이터 요청 및 업데이트

  • 상태 변화에 따라 수동으로 데이터를 요청하고, setState를 사용하여 수동으로 업데이트 해야함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { IPagination } from "models/pagination.model";

export const useBooks = () => {
  const { search } = useLocation();

  const [books, setBooks] = useState<IBook[]>([]);
  const [pagination, setPagination] = useState<IPagination>({
    total_count: 0,
    current_page: 1
  });

  useEffect(() => {
    const params = new URLSearchParams(search);

    const categoryIdParams = params.get(QUERYSTRING.CATEGORY_ID);
    const newsParams = params.get(QUERYSTRING.NEWS);
    const currentPageParams = params.get(QUERYSTRING.PAGE);

    fetchBooks({
      category_id: categoryIdParams ? Number(categoryIdParams) : undefined,
      new: newsParams ? true : undefined,
      page: currentPageParams ? Number(currentPageParams) : 1,
      limit: LIMIT
    }).then((res) => {
      setBooks(res.lists);
      setPagination(res.pagination);
    });
  }, [search]);

  return { books, pagination };
};

useQuery 사용했을 때

1. 자동 데이터 관리

  • useQuery는 데이터 요청과 업데이트를 자동으로 처리
  • 쿼리의 상태 변화에 따라 자동으로 데이터를 요청하고, 데이터가 업데이트 되면 자동으로 컴포넌트를 렌더링 시킴

2. 데이터 요청 관련 상태 제공

  • isLoading, isPending, isSuccess 등 데이터 요청 상태에 대한 값을 제공
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
export const useBooks = () => {
  const { search } = useLocation();

  const params = new URLSearchParams(search);

  const { data: booksData, isLoading: isBooksLoading } = useQuery({
    queryKey: ["books", search],
    queryFn: () => {
      const categoryIdParams = params.get(QUERYSTRING.CATEGORY_ID);
      const newsParams = params.get(QUERYSTRING.NEWS);
      const currentPageParams = params.get(QUERYSTRING.PAGE);

      fetchBooks({
        category_id: categoryIdParams ? Number(categoryIdParams) : undefined,
        new: newsParams ? true : undefined,
        page: currentPageParams ? Number(currentPageParams) : 1,
        limit: LIMIT
      });
    }
  });

  return {
    books: booksData?.lists,
    pagination: booksData?.pagination,
    isEmpty: booksData?.lists.length === 0,
    isBooksLoading
  };
};
This post is licensed under CC BY 4.0 by the author.