해당 프로젝트는
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
구체적인
type
과api
요청 메서드들은 작성하기에는 자리를 많이 차지해서 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;