구현 조건
1. axios 대신 기본 fetch 를 사용
2. Authorization 에 accessToken 을 담아서 보내는 AuthFetch, 그냥 기본 fetch 인스턴스가 필요함
3. 로그인 후 받은 accessToken은 cookie 에 httpOnly 쿠키로 저장 -> js 단에서 가져다 쓸 수 없음
- httpOnly 쿠키로 저장하지 않으면, next-cookie 의 getCookie() 등으로 자유롭게 가져다 쓸 수 있다.
4. next-auth의 useSession 을 통해서 accessToken 에 접근 가능
- next-auth 의 useSession 을 통해서 접근할 수 있기 때문에 use** 형태의 훅 내부에 메서드를 사용하는 패턴으로 구현
단계별 구현
1. 기본 fetch 로 Client Side 데이터 fetch
// 1. 기본 fetch
useEffect(() => {
fetch("https://api.coinpaprika.com/v1/coins")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
});
}, []);
2. 메서드의 재사용성을 위하여 hook 내부 method 로 구조 변경
1) useCheckout.ts 훅 생성
export const useCheckout = () => {
const getCoins = () => fetch("https://api.coinpaprika.com/v1/coins").then((response) => response.json());
return { getCoins };
};
2) 컴포넌트에서 메서드 호출
const { getCoins } = useCheckout();
// 2. useCheckout 내부 method 사용
useEffect(() => {
getCoins().then((data) => {
console.log("getCoins - data", data);
});
}, []);
3. accessToken 를 헤더에 함께 보내는 AuthFetcher 사용
1) useFetchWithToken 훅 생성
import { useSession, signIn } from "next-auth/react";
interface FetchOptions extends RequestInit {
headers?: HeadersInit;
}
export default function useFetchWithToken() {
const { data: session, status } = useSession();
const fetchWithToken = async (url: string, options: FetchOptions = {}): Promise<Response | undefined> => {
if (status === "loading") {
return;
}
if (status === "unauthenticated") {
console.log("로그인이 필요합니다.");
return;
}
const headers = {
...options.headers,
"Content-Type": "application/json",
Authorization: `Bearer ${(session?.user as any).accessToken}`,
};
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_CHAT_SERVER_API_URL}${url}`, {
...options,
headers,
});
if (response.status === 401) {
signIn();
}
return response;
} catch (error) {
console.error("Fetch error:", error);
// Handle error accordingly
}
};
return fetchWithToken;
}
2) useCheckout 훅에 method 추가 (fetchWithToken 을 사용하는 fetcher 메서드)
export const useCheckout = () => {
const authFetcher = useFetchWithToken();
const getCoins = () => fetch("https://api.coinpaprika.com/v1/coins").then((response) => response.json());
const getAuthCoins = () => authFetcher("https://api.coinpaprika.com/v1/coins").then((response) => response.json());
return { getCoins, getAuthCoins };
};
3) 컴포넌트 단에서 호출
// 3. authFetcher 사용
const { status } = useSession();
useEffect(() => {
getAuthCoins().then((data) => {
console.log("getAuthCoins - data", data);
});
}, [status]);
4) 요청 헤더에 Authorization 확인 가능
4. react-query 사용하여 기본 fetch 호출
1번 로직에 react-query 추가
// 4. useQuery 사용(기본 fetch)
const { data } = useQuery({
queryKey: ["test"],
queryFn: () => fetch("https://api.coinpaprika.com/v1/coins").then((res) => res.json()),
});
useEffect(() => {
console.log("useQuery - data", data);
}, [data]);
5. react-query 에서 hook 내부 method 호출
2-1 에서 만들어 둔 getCoins 함수를 호출하여 사용
// 5. useQuery 사용(useCheckout 내부 method 사용)
const { data } = useQuery({
queryKey: ["test"],
queryFn: getCoins,
});
useEffect(() => {
console.log("getCoins - data", data);
}, [data]);
6. react-query 를 통해서 accessToken 를 헤더에 함께 보내는 AuthFetcher 사용
3-1 에서 만들어둔 useFetchWithToken 훅을 사용
useCheckout.ts
useFetchWithToken.ts
동일하게 사용
컴포넌트 부분 코드 변경 -> useQuery 키값에 status 를 넣어서 세션의 status 값이 변경될때 재시도 될 수 있도록 함
// 6. useQuery 사용(authFetcher 사용)
const { status } = useSession();
const { data } = useQuery({
queryKey: ["test2", status],
queryFn: getAuthCoins,
});
useEffect(() => {
console.log("getCoins - data", data);
}, [data]);
<최종 모습>
1. MyComponent.tsx
import { useSession } from "next-auth/react";
import { useQuery } from "@tanstack/react-query";
import { useCheckout } from "@/shared/queries/checkout";
const { status } = useSession();
const { getAuthCoins } = useCheckout();
const { data } = useQuery({
queryKey: ["test2", status],
queryFn: getAuthCoins,
});
useEffect(() => {
console.log("getCoins - data", data);
}, [data]);
2. useCheckout.ts
import useFetchWithToken from "@/hooks/useFetchWithToken";
export const useCheckout = () => {
const authFetcher = useFetchWithToken();
const getCoins = () => fetch("https://api.coinpaprika.com/v1/coins").then((response) => response.json());
const getAuthCoins = () => authFetcher("/service/customers/niceid/oauth/token").then((response) => response.json());
return { getCoins, getAuthCoins };
};
3. useFetchWithToken.ts
import { useSession, signIn } from "next-auth/react";
interface FetchOptions extends RequestInit {
headers?: HeadersInit;
}
export default function useFetchWithToken() {
const { data: session, status } = useSession();
const fetchWithToken = async (url: string, options: FetchOptions = {}): Promise<Response | undefined> => {
if (status === "loading") {
return;
}
if (status === "unauthenticated") {
console.log("로그인이 필요합니다.");
return;
}
const headers = {
...options.headers,
"Content-Type": "application/json",
Authorization: `Bearer ${(session?.user as any).accessToken}`,
};
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_CHAT_SERVER_API_URL}${url}`, {
...options,
headers,
});
if (response.status === 401) {
signIn();
}
return response;
} catch (error) {
console.error("Fetch error:", error);
// Handle error accordingly
}
};
return fetchWithToken;
}
'programming > React' 카테고리의 다른 글
Next.js App Router SSR 용 fetch 코드 (헤더 쿠키, Authorization 세팅) (0) | 2024.08.13 |
---|---|
Next.js with Typescript 프로젝트 테스트 코드 작성하기(jest, testing-library/react) (0) | 2024.06.17 |
Next.js 의 Fast Refresh 는 하나의 브라우저 세션과만 동작한다 (0) | 2024.05.24 |
Next App Router 환경 + MSW 설정 (0) | 2024.04.09 |
리액트 에러 바운더리를 사용하여 에러 핸들링하기 (+ react-query) (0) | 2024.02.05 |