우리 팀의 경우 Recoil을 이용해서 데이터를 관리하고 msw를 이용해 목서버를 만들어서 사용 중이었다, 하지만 Recoil이나 Redux 등의 라이브러리는 클라이언트의 데이터들을 관리하기에는 좋아도 서버의 데이터들을 관리하기에는 적합하지 않다는 이야기를 접했고 서버에서 주는 데이터들을 좀 더 적합하고 효과적인 구현을 위해 리액트쿼리를 공부하게 되었다.
리액트쿼리란 데이터 Fetching, 캐싱, 동기화, 서버 쪽 데이터 업데이트 등을 쉽게 만들어 주는 React 라이브러리이다.
데이터를 get해올 때는 useQuery를, poat, patch, delete할 때는 useMutation을 사용한다.
데이터를 get해오는 최종코드는 아래와 같다.
userComment.tsx
const { data } = useQuery(["beatId",beatId], ()=>getComment(beatId)
, {
refetchOnWindowFocus: false,
retry: 0,
onSuccess: data => {
if (data?.status === 200) {
// console.log(data);
// console.log("성공");
setComments(data?.data.data.commentList)
}
},
onError: error => {
console.log("실패");
}
});
trackPost.ts
export async function getComment(props:number) {
const state=props
try {
const data = await axios.get(`${process.env.REACT_APP_BASE_URL}/tracks/comments/8?page=1&limit=20`,
{
headers: {
Authorization: `Bearer ${`${process.env.REACT_APP_PRODUCER_ACCESSTOKEN}`}`,
},
});
// data && console.log(data);
return data;
} catch (e) {
console.log(e);
}
}
refetchOnWindowFocus는 window가 다른 곳을 왔다가 돌아왔을 때의 재실행 여부를, retry는 실패 시 재호출 여부를 뜻한다.
const { data } = useQuery(()=>getComment(beatId)
사실 이렇게 한 줄만 작성해도 데이터는 잘 받아와진다, 최종코드가 위와 같이 길어진 이유에는 두 가지 이유가 있다.
첫째, 데이터가 바로바로 반영되지 않았다. 우리 서비스에는 음악카테고리를 클릭하면 클릭한 카테고리에 따라 해당하는 트랙을 바로바로 보여줘야하는데, 위의 한 줄 코드로는 데이터가 바로바로 반영되지 않았다. 음악 카테고리를 클릭한 후 한참 뒤에야 데이터가 반영되는 것이 아닌가! 그래서 리액트쿼리의 unique key 를 이용했다.
const { data } = useQuery(["beatId",beatId], ()=>getComment(beatId)
이렇게 "beatId"라는 unique key를 부여해주었다. ["beatId", beatId] 에서 첫번째 파라미터인 "beatId"는 해당 쿼리를 의미하는 unique key이고, 두번째 파라미터인 beatId는 비동기함수이다. 즉, 두번째 파라미터인 beatId가 변경될 때마다 data를 불러오는 쿼리가 재실행된다!
둘째, 데이터를 가져오는데에 성공하거나 실패했을 때의 액션을 표현하기에 위의 한 줄은 부족했다. 원래는 그냥 .then이나 await을 이용해서 처리를 해주었었는데, 이번에는 리액트쿼리에 내장되어있는 onSuccess, onError를 이용해서 액션을 구현해주었다.
데이터를 Post하는 최종코드는 아래와 같다.
useComment.tsx
원클릭 이벤트 함수
function uploadComment(uploadData:UploadDataType){
setIsCompleted(true);
console.log("1")
}
useEffect(()=>{
if(content&&wavFile){
if(uploadData.content&&uploadData.wavFile){
console.log("2")
let formData = new FormData();
formData.append("wavFile", wavFile);
formData.append("content", content);
mutate(formData)
}
},[isCompleted])
mutate
const queryClient = useQueryClient();
const {mutate} = useMutation(postComment, {
onSuccess: () => {
console.log("3")
queryClient.invalidateQueries("beatId");
setUploadData({
content: "",
wavFile: null,
});
setContent("")
setWavFile(null)
setIsCompleted(false)
setIsEnd(false)
}
});
여기서 queryClient.invalidateQueries("beatId"); 는 post에 성공하면 "beatId"라는 unique key를 가진 쿼리를 재실행한다는 뜻이다. post에 성공하면 자동적으로 get을 실행하도록 한다.
trackPost.ts
export async function postComment(formData:any) {
try {
const data=await axios.post(`${process.env.REACT_APP_BASE_URL}/tracks/8`, formData,
{
headers: {
'Content-Type': 'amultipart/form-data',
Authorization: `Bearer ${`${process.env.REACT_APP_VOCAL_ACCESSTOKEN}`}`,
},
});
data && console.log(data);
} catch (e) {
console.log(e);
}
}
사실 post에서 고생을 좀 많이 했다. 버튼을 클릭하면 원클릭 함수가 발생하고, 그 때 useMutation이 돌아야하는데, 원클릭 함수 안에 useMutation을 집어넣으면 상당한 에러가 뜨기 때문. useMutation을 따로 쓴 함수를 작성하고, 원클릭했을 때 mutate(formData)와 같은 방식으로 불러와주니 해결되었다. 또한, 콘솔에 찍으면 잘 나오던 데이터들이 막상 post하니까 자꾸 null값으로 찍혔는데, 최종적으로 post할 데이터들을 recoil로 저장해준 뒤 post하니 해결되었다.
사실 그동안 아주 기본적인 라이브러리로도 웬만한 구현이 가능했기에 리액트쿼리를 적용할 생각을 하지 않았다. 하지만, 우리 팀의 서비스는 카테고리를 클릭하면 클릭한대로 데이터를 필터링해주어야하고, 댓글을 작성하면 바로바로 get이 되어야하는 서비스였다. 리액트쿼리가 없으면 안되는 상황에서 억지로 리액트쿼리를 공부하게 되었는데 데이터에 변화를 주면 바로 반영이 되고, post를 하면 바로 get이 되는 등 리액트 쿼리의 장점을 알게 되니 이전으로 돌아갈 수 없을 것만 같다.
react-query overview
리액트쿼리 개념 및 정리
리액트쿼리 캐싱 이슈 해결하기
리액트쿼리 사용법 및 사용이유
리액트쿼리로 파일 다운로드 구현하기
useMutation 사용하기
yunsoho 깃허브
useMutation 알아보기
리액트쿼리의 라이프사이클
리액트쿼리 장점
안녕하세요. 포스트보다가 궁금한게 있어서 댓글드립니다.
코드에 보면 JWT 토큰전달하려고 헤더에 아래 코드를 반복적으로 넣고 있는데요.
Authorization:
Bearer ${
${process.env.REACT_APP_VOCAL_ACCESSTOKEN}}
,이부분을 공통으로 사용할수도있나요?