가수면

[React Query] useQuery 본문

React/라이브러리

[React Query] useQuery

니비앙 2023. 1. 6. 18:15

설치

 

npm i @tanstack/react-query

npm i @tanstack/react-query-devtools

 

QueryClient와 QueryClientProvider 추가 (ReactQueryDevtools은 선택 사항)

import { Posts } from "./Posts";
import "./App.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

// 추가
const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <div className="App">
        <h1>Blog Posts</h1>
        <Posts />
      </div>
      <ReactQueryDevtools />
    </QueryClientProvider>
  );
}

export default App;

 

useQuery

import { useQuery } from "@tanstack/react-query";
.
.
.
async function fetchPosts() {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=10&_page=0");
  return response.json();
}

  // useSelector 같은 기능
  // 인자, 사용할 함수 이름, stale이라고 판단할 시간 설정(밀리초 단위))
  const { data, isError, isLoading, error } = useQuery(["posts"], fetchPosts, { staleTime: 2000 });
  // 비동기 처리에 대한 대처
  //if (!data) return <div />;	세련된 방법은 아님
  if (isLoading) return <h3>로딩 중</h3>;
  if (isError)
  return (
    <>
      <h3>알 수 없는 오류</h3>
      <p>{error.toString()}</p> // 세련된 방법은 아님
    </>
  );
.
.
. 
map 돌림

 

디테일 페이지 데이터 불러올 때 / 쿼리 함수에 값을 실어야할 경우

// 의존성 배열 + 객체 느낌. post.id가 업데이트되면 새 쿼리를 생성, 데이터가 바뀌면 키도 바뀌도록
const { data, isLoading, isError } = useQuery(["comments", post.id], () => fetchComments(post.id));

 

useQuery옵션


select

원래 return되었을 데이터를 가져와서 변환한 다음 변환한 데이터를 return해줌

  const selectFn = useCallback(
    (data) => {
      getAvailableAppointments(data, user);
    },
    [user]
  );

  const { data: appointments = [] } = useQuery(
    [queryKeys.appointments, monthYear.year, monthYear.month],
    () => getAppointments(monthYear.year, monthYear.month),
    {
      select: showAll ? undefined : selectFn,
    }
  );

 

 

refetch 옵션 끄기

정적인 페이지에서는 비용 / 성능 측면에서 모두 네트워크와 통신할 필요가 없음

 

  export function useTreatments() {
    const { data = [] } = useQuery(queryKeys.treatments, getTreatments, {
      staleTime: 600000,
      cacheTime: 900000,
      refetchOnMount: false,
      refetchOnWindowFocus: false,
      refetchOnReconnect: false,
    });
    return data;
  }


refetchInterval

refetchInterval: 60000,

 

enabled

export function useUserAppointments(): Appointment[] {
  const { user } = useUser();
  const { data: useAppointments = [] } = useQuery(
    'user=appointments',
    () => getUserAppointments(user),
    { enabled: !!user },
  );
  return useAppointments;
}

로그인 했을 때 키를 쥐어주고 그 화면 불러온 뒤

  function clearUser() {
    queryClient.setQueryData(queryKeys.user, null);
    queryClient.removeQueries('user-appointments');
  }

불필요할 땐 키를 제거해 렌더링을 안불러오게 함

 

 

Uncaught TypeError: Cannot read properties of undefined (reading 'map') 에러가 뜰 경우

// 빈 배열을 할당해줌
const { data = [] } = useQuery(queryKeys.treatments, getTreatments);

 

 

데이터를 다시 가져오게되는 트리거 (리페칭)

  • 컴포넌트 재 마운트
  • 윈도우 재 포커스
  • useQuery에서 반환되어 수동으로 리페칭할 때
  • 지정된 간격으로 리페칭이 자동 실행될 때
  • Mutation을 생성한 뒤 쿼리를 무효화할 때
  • 클라이언트의 데이터가 서버의 데이터와 불일치할 때

 

페이지 네이션

async function fetchPosts(pageNum) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${pageNum}`);
  return response.json();
}

  const { data, isError, isLoading, error } = useQuery(["posts", currentPage], () => fetchPosts(currentPage), {
    staleTime: 2000,
    //쿼리 키가 바뀌어도 이전 데이터 유지
    keepPreviousData: true,
  });
  .
  .
  .
  <div className="pages">
        <button disabled={currentPage <= 1} onClick={() => setCurrentPage(currentPage - 1)}>
          Previous page
        </button>
        <span>Page {currentPage}</span>
        <button disabled={currentPage >= maxPostPage} onClick={() => setCurrentPage(currentPage + 1)}>
          Next page
        </button>
      </div>

 

prefetchQuery

데이터를 미리 캐싱할 수 있게 해주는 메소드

  const queryClient = useQueryClient();

  useEffect(() => {
    if (currentPage < maxPostPage) {
      const nextpage = currentPage + 1;
      queryClient.prefetchQuery(["posts", nextpage], () => fetchPosts(nextpage));
    }
  }, [currentPage, queryClient]);

커스텀 훅으로 만들 시 useEffect 내에서 훅을 실행할 수 없음

staleTime이나 cachTime 등의 옵션으로 조절

export const usePrefetchTreatments = (): void => {
  const queryClient = useQueryClient();
  queryClient.prefetchQuery(queryKeys.treatments, getTreatments);
};


-Home.tsx-

export function Home(): ReactElement {
  usePrefetchTreatments();
  return (

 

에러 핸들링

queryClient에 디폴트 값으로 줌으로써  전역적인 에러 핸들링을 할 수 있다.

import { QueryClient } from 'react-query';

function queryErrorHandler(error: unknown): void {
  const title =
    error instanceof Error ? error.message : 'error connecting to server';
return 에러처리하고 싶은 방식
}

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      onError: queryErrorHandler,
    },
  },
});

onError

  const {data = [], isError, isLoading, error} = useQuery(["main"], mainApi.read, {
    onError: (error) => {
      const { status } = error?.response.request;
      if (status === 401) {
        dispatch(showModal({ isModal: true, content: "로그인 후 이용해주세요.", move: "/login" }));
      } else if (status === 400)
        return dispatch(showModal({ isModal: true, content: "일기장 조회에 실패했습니다.", move: "/login" }));
    },
  });

위와 같이 코드를 짜면 에러 발생 시 바로 모달창이 뜨지 않고 리패칭해야만 모달창이 뜨게 된다.

해결법은 리렌더링을 일으키는 것.

// isError일 때 태그를 생성함으로써 리렌더링을 일으킴
  return (
    <>
      {isLoading ? (
        <h2>로딩 중...</h2>
      ) : isError ? (
        <h2>{`${error?.response.status} ERROR`}</h2>
      ) : (~~~

 

Comments