blegram(3) - 데이터 패칭 ( react-query )
포스트
취소

blegram(3) - 데이터 패칭 ( react-query )

해당 프로젝트는 Next.js + TypeScript를 기반으로 만드는 인스타그램 클론 개인 프로젝트입니다.

해당 포스트는 프로젝트에서 react-query를 사용한 몇 가지 방법에 대해 정리하는 포스트입니다.
개발을 진행하면서 작성된 내용이 수정될 수 있습니다.

🦴 세팅

react-query를 사용하기 위해 provider를 등록하면서 성공과 실패에 대한 핸들러를 등록하는 코드입니다.
구체적인 성공/실패 핸들러가 등록되어있지 않다면 전역적으로 등록한 핸들러가 실행됩니다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { AxiosError } from "axios";
import { QueryClientProvider, QueryClient } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import { toast } from "react-toastify";

/** 2023/03/30 - 에러 처리 핸들러 - by 1-blue */
const queryErrorHandler = (error: unknown) => {
  let message = "알 수 없는 오류가 발생했습니다.";

  if (error instanceof AxiosError) {
    message = error.response?.data.message;
  } else if (error instanceof Error) {
    message = error.message;
  }

  toast.warning(message);
};

/** 2023/03/30 - 성공 처리 핸들러 - by 1-blue */
const querySuccessHandler = (data: unknown) => {
  if (
    typeof data === "object" &&
    data &&
    "message" in data &&
    typeof data.message === "string"
  ) {
    toast.success(data.message);
  }
};

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      onError: queryErrorHandler,
      onSuccess: querySuccessHandler,
      staleTime: 1000 * 60 * 15, // 15 분
      cacheTime: 1000 * 60 * 10, // 10 분
    },
    mutations: {
      onError: queryErrorHandler,
      onSuccess: querySuccessHandler,
    },
  },
});

/** 2023/03/26 - "react-query" Provider 적용 - by 1-blue */
const MyReactQueryProvider: React.FC<React.PropsWithChildren> = ({
  children,
}) => (
  <QueryClientProvider client={queryClient}>
    <ReactQueryDevtools initialIsOpen position="top-left" />
    {children}
  </QueryClientProvider>
);

export default MyReactQueryProvider;

🖌️ Custom Hook

구체적인 typeapi 요청 메서드들은 작성하기에는 자리를 많이 차지해서 GitHub에서 확인해주세요!

useQuery()에서 key로 넣어줄 인자로 사용할 값입니다.

쿼리 무효화를 하는 경우 접두사가 일치하는 모든 쿼리를 같이 무효화할 수 있기 때문에 같은 이름의 변수로 사용하기 위해 따로 분리해서 작성했습니다.

1
2
3
4
/** 2023/03/30 - "react-query"의 key - by 1-blue */
export const queryKeys = {
  user: "user",
};

0️⃣ GET을 사용하는 훅

쿼리 무효화와 고유한 키를 갖기 위해서 queryKeys.user를 사용합니다.
응답 성공과 실패에 대한 공통된 처리(토스트 메시지)는 위의 QueryClient에서 처리해줬습니다.
그리고 나머지는 각자의 컴포넌트에서 처리합니다.

대표적으로 사용한 형태로 하나를 가져왔고 나머지는 유사한 형태로 계속 생성해서 사용했습니다.

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
import { useQuery } from "react-query";

// api
import { apiServiceUser } from "@src/apis";

// key
import { queryKeys } from ".";

// type
import type { ApiFetchUserResponse } from "@src/types/api";

interface UseUserHandler {
  (nickname: string): {
    user?: ApiFetchUserResponse["user"];
    isFetchingUser: boolean;
  };
}

/** 2023/03/29 - 특정 유저 정보를 얻는 훅 - by 1-blue */
const useUser: UseUserHandler = (nickname) => {
  const { data, isLoading } = useQuery<ApiFetchUserResponse>(
    [queryKeys.user, nickname],
    () => apiServiceUser.apiFetchUser({ nickname })
  );

  return { user: data?.user, isFetchingUser: isLoading };
};

export default useUser;

1️⃣ GET을 제외한 훅 ( mutate )

POST, PATCH, DELETE 등의 메서드는 특정 이벤트에 의해서 동작해야 하기 때문에 mutate로 처리합니다.

mutate는 요청을 수정하는 목적으로 사용하는 것으로 알고 있어서 로그인 같은 경우는 굳이 react-query를 거쳐서 요청할 필요는 없지만, 모든 코드의 일관성을 위해서 모든 네트워크 요청은 react-query를 사용했습니다.

mutate 또한 QueryClient에서 처리했기 때문에 자동으로 토스트 메시지가 렌더링되게 됩니다.

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
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useMutation } from "react-query";

// api
import { apiServiceAuth } from "@src/apis";

// type
import type { UseMutateFunction } from "react-query";
import type { ApiLogInRequest, ApiLogInResponse } from "@src/types/api";

/** 2023/03/30 - 로그인 요청 훅 - by 1-blue */
const useLogIn = (): UseMutateFunction<
  ApiLogInResponse,
  unknown,
  ApiLogInRequest,
  unknown
> => {
  const router = useRouter();
  const { mutate, isSuccess } = useMutation(apiServiceAuth.apiLogIn);

  /** 2023/03/30 - 로그인 성공 시 메인 페이지로 리다이렉트 - by 1-blue */
  useEffect(() => void (isSuccess && router.replace("/")), [isSuccess, router]);

  return mutate;
};

export default useLogIn;

📮 레퍼런스

  1. react-query

  2. GitHib - blegram

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.