api문서가 거의다 나옴으로써 axios와 react-query를 설계해서 컨밴션처럼 다들 동일하게 사용할 수 있도록 세팅하는 작업이 필요했고
그 부분은 내가 맡아서 진행하게 되었다
고려해야 할 점
- axios 인터셉터를 사용해서 미들웨어 기능을 구현할 것( 토큰값에 따른 redirect 혹은 Bearer토큰 header에 추가 )
- 토큰이 만료되었을 시 자동으로 로그인 페이지로 이동
- 각 페이지 마다 api호출로직들을 모아서 유지보수하기 편리하게 할 것
- react-query로직을 커스텀훅으로 분리해서 가져다 쓰면 바로 되도록 설계할 것
크게 위에 사항들을 고려해야 했고 팀원들이 react-query는 처음이라 직관적으로 설계하도록 노력했다
접근 방법 및 해결법
먼저 axios는 기본 create()를 활용해서 instance를 만들고 이를 export default해서 가져다가 사용하도록 했다
import { getCookie } from '@utils/cookie';
import axios from 'axios';
const instance = axios.create({
baseURL: 'https://api.com/',
timeout: 5000,
withCredentials: true,
});
instance.interceptors.request.use(
(config) => {
const token = getCookie('accessToken');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
return config;
} else {
window.location.href = '/login';
return config;
}
},
(error) => {
if (error.response.status === 401) {
window.location.href = '/login';
}
},
);
instance.interceptors.response.use(
(config) => {
const token = getCookie('accessToken');
if (!token) {
window.location.href = '/login';
return config;
}
},
(error) => {
if (error.response.status === 401) {
window.location.href = '/login';
}
},
);
export default instance;
request와 respone시 항상 토큰을 체크하고 오류 발생 시 401(인증 오류)면 login페이지로 이동하도록 했다
그리고 매번 header에 token을 함께 전달하기 위해서 쿠키에 토큰이 있다면 해당 요청헤더에 Bearer토큰으로 추가 하도록 했다
다음은 axios를 활용한 api로직이다 경로는 api/group/group-request.ts 로 api안에 각 페이지별 폴더 안에 위치 시켰다type.ts파일도 함께 위치시켜서 해당 api의 request와 response타입을 모아둘 수 있도록 했다
// api/group/group-request.type.ts
// requset
// response
export interface TGroupFetchInfo {
groupId: number;
groupName: string;
groupCode?: string;
sortNum?: number;
admin?: boolean;
}
export type TGroupFetchRes = TGroupFetchInfo[];
export interface TGroupFetchMemberInfo {
id: number;
nickname: string;
profileImageUrl: string;
}
export type TGroupMemberFetchRes = TGroupFetchMemberInfo[];
export interface TGroupUpdateRes {
groupId: number;
groupName: string;
code: string;
groupState: string;
createdAt: string;
modifiedAt: string | null;
}
export interface TGroupCreateRes {
id: number;
groupName: string;
code: string;
groupState: string;
createdAt: string;
modifiedAt: string | null;
}
// api/group/group-request.ts
import axios from '@api/axios';
const groupRequest = {
fetchGroup: async () => {
try {
const { data } = await axios.get(`user/group/view/myList`);
return data;
} catch (error) {
return error;
}
},
fetchGroupMember: async (groupId: number) => {
try {
const { data } = await axios.get(`user/group/view/${groupId}/members`);
return data;
} catch (error) {
return error;
}
},
createGroup: async (groupData: string) => {
try {
const { data } = await axios.post('user/group/create', { groupName: groupData });
return data;
} catch (error) {
return error;
}
},
updateGroup: async (groupName: string, groupId: string) => {
try {
const { data } = await axios.patch(`user/group/modify/${groupId}`, { groupName });
return data;
} catch (error) {
return error;
}
},
} as const;
export default groupRequest;
groupRequest라는 객체안에 api메서드들을 모아서 객체로 접근해서 호출 할 수 있도록 설계했고
response.data로 안하고 구조분해할당으로 data만 추출했다 추후 response.status 등 다른 값이 필요하다면 필요에 따라 수정할 수 있도록 전달했고 이름도 직관적으로 지었다
이 axios를 사용 하는 react-query 로직을 커스텀훅으로 빼서 hooks/react-query/use-query-group.ts 경로 형식으로 모아놨다
/* eslint-disable no-console */
import groupRequest from '@api/group/group-request';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
export const useGroupQuery = () => {
const query = useQuery({
queryKey: [`groupList`],
queryFn: async () => await groupRequest.fetchGroup(),
});
return query;
};
export const useGroupMemberQuery = (groupId: number) => {
const query = useQuery({
queryKey: [`groupMember`, groupId],
queryFn: async () => await groupRequest.fetchGroup(),
});
return query;
};
export const useGroupCreateMutation = () => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (groupData: string) => await groupRequest.createGroup(groupData),
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: [`groupList`] });
},
onError: (error) => console.log(`그룹 생성 에러: ${error}`),
});
return mutation;
};
export const useGroupUpdateMutation = (groupId: string) => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (groupName: string) => await groupRequest.updateGroup(groupName, groupId),
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: [`group`, groupId] });
queryClient.invalidateQueries({ queryKey: [`groupList`] });
},
onError: (error) => console.log(`그룹명 수정 에러: ${error}`),
});
return mutation;
};
각 onError에는 발생한 에러가 어떤 query에서 발생한 에러인지 명시해줬고
useQuery인 경우 쿼리키와 쿼리함수를 가져와서 적용시켜주고
useMutation은 성공 시 관련된 쿼리키를 refetch시켜줘서 ui에 바로 업데이트 시켜줬다
이런 설계흐름을 적용 시킨 컴포넌트 중 유저 정보를 보여주는 GNB쪽에서 작성된 예시를 보면
import { useEffect } from 'react';
import ProfileModalButton from '@components/modal/profile-modal/profile-modal-button';
import { useUserNicknameUpdateMutation, useUserQuery } from '@hooks/react-query/use-query-user';
import { createRandomNickName } from '@utils/createRandomNickname';
import styled from 'styled-components';
const Profile = () => {
const { data: userData, isLoading, isError, isSuccess } = useUserQuery();
const { mutateAsync } = useUserNicknameUpdateMutation();
useEffect(() => {
if (isSuccess && userData?.nickname.length === 0) {
mutateAsync(createRandomNickName());
}
}, [isSuccess, userData, mutateAsync]);
if (isLoading) return 'Loading...';
if (isError) return 'Error...';
return (
<ProfileModalButton>
<S.ProfileBox>
<S.NickNameBox>{userData.nickname}</S.NickNameBox>
<S.ProfileImgBox>
<img src={userData.profileImageUrl} alt="test" />
</S.ProfileImgBox>
</S.ProfileBox>
</ProfileModalButton>
);
};
export default Profile;
...
위처럼 커스텀훅으로 query와 mutation을 편하게 가져와서 쓸 수 있도록 설계했다
컴포넌트에서 커스텀훅 호출 -> react-query작동 및 해당하는 api함수 실행 -> axios로 데이터 통신 -> 성공 시 react-query로 인해 데이터 캐싱 혹은 뮤테이션 -> 결과값 컴포넌트에 반환
위와 같은 흐름으로 설계를 해서 컴포넌트에서는 가져다가 쓸 로직만 구성하면 되도록 했다
이후 작업
api관련 설계가 끝났으니 이제 이를 적용해서 필요한 모든 곳에 적용해볼 예정이다
'개발일지 > React.js & Node.js' 카테고리의 다른 글
| EFFI 프로젝트 #9 API 에러핸들링 (0) | 2024.06.15 |
|---|---|
| EFFI 프로젝트 #8 API 적용 (0) | 2024.06.14 |
| EFFI 프로젝트 #6 css리펙토링 및 글자가변크기 트러블슈팅 (0) | 2024.06.09 |
| EFFI 프로젝트 #5 그룹모달 디자인 적용 및 모달버튼 공용컴포넌트 분리 (0) | 2024.06.06 |
| EFFI 프로젝트 #4 nav바 side바 디자인 적용(말풍선+textoverflow 체크) (0) | 2024.06.04 |