작년 초 쯤이었을까?
api 요청 및 응답 상태 관리를 redux-thunk에서 react-query로 변경해나가기 시작했다.
const getBookApi = async () => {
const response = await axios.get(URL)
return response.data
};
const {data} = useQuery('book', getBookApi)
의식의 흐름대로 다음과 같이 썼었는데...
디버깅을 하다보니, 응답에 실패했는데도 아무 곳에서도 에러를 감지할 수가 없었다.
분명 useQuery의 onError 옵션을 통해, 에러 발생을 catch할 수 있다고 되어있는데도!
하지만 곰곰히 생각해보면, 당연한 일이다.
왜? useQuery는 두번째 인자로 넘겨준 queryFn이 반환하는 Promise가 rejected 상태일 경우, onError를 실행하는데...
현재 getBookApi 함수는 axios 요청시 HTTP error가 발생하더라도
→ response.data, 즉 undefined가 담긴 fulfilled된 Promise를 return할 뿐이니까!
그럼 어쩌지? → 아하! getBookApi도 axios 요청이 실패할 경우, error를 반환하면 되지 않을까?
const getBookApi = async () => {
try {
const response = await axios.get(URL)
return response.data
} catch(e) {
return e.response
}
};
const {data} = useQuery('book', getBookApi)
당연히 안된다!
이는 error 발생 시 getBookApi의 반환값이 Promise<e.response>로 바뀐 것 뿐이니까!
(해당 Promise는 여전히 fulfilled일테다)
그럼 또 어쩌지?
const getBookApi = async () => {
try {
const response = await axios.get(URL)
return response.data
} catch(e) {
throw e.response // throw를 통해 해당 Promise를 reject
}
};
const {data} = useQuery('book', getBookApi, {
onError : (e) => alert(e)
})
초 간단!
HTTP error 발생 시, catch문이 실행되고,
해당 error를 throw해줌으로서, getBookApi는 rejected상태의 Promise를 반환할 것이다.
→ queryFn이 실패했음이 전파되면서, useQuery의 onError 옵션을 통해 해당 에러를 핸들링해줄 수 있다.
그런데 이게 정말 좋은 코드인가?
getBookApi 함수는 어떤 역할을 수행해야할까?
→ axios를 통해 HTTP 요청을 보내고, 해당 응답을 반환하는 것.
즉... 해당 요청의 성공/실패 여부는 getBookApi 함수의 관심사라 볼 수 없다..!
그렇다면? 함수 내부에서 try-catch문을 사용해 불필요한 에러 전파를 유도할 필요가 전혀 없지 않은가?
const getBookApi = async () => {
return await axios.get(URL)
};
const {data} = useQuery('book', getBookApi, {
onError : (e) => alert(e)
})
이런 식으로!
getBookApi는 axios에서 반환되는 AxiosResponse나 AxiosError 등을 그대로 return해주면 될테다.
axios는 error 발생 시 rejected 상태의 Promise를 반환하므로, useQuery에서도 의도처럼 queryFn의 요청 실패를 감지할 수 있는 것!
하지만 위와 같이 작성할 경우, (또) 문제가 있다.
const getBookApi = async () => {
return await axios.get(URL)
};
const {data} = useQuery('book', getBookApi, {
onError : (e) => alert(e)
})
console.log(data) // AxioseResponse
console.log(data.data) // {id : 0, ...}
useQuery에서는 queryFn의 반환값을 data라는 값으로 사용할 수 있는데...
해당 요청이 성공했을 경우, data에는? AxiosResponse가 담겨있을테다!
(axios.get의 응답을 그대로 return했으므로)
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
config: AxiosRequestConfig<D>;
request?: any;
}
server에서 반환한 데이터는 AxiosResponse.data에 담겨있기 때문에
위와 같이 작성할 경우 data.data와 같은 형태로 해당 값을 조회해야한다!
그리고 매번 .data로 접근하는건? 굉장히 유쾌하지 못하다 :(
(실수 여지↑, 의도 파악 어려움 등...)
const getBookApi = async () => {
return await axios.get(URL)
};
const {data} = useQuery(
'book',
async () => {
const { data } = await getBookApi()
return data
},
{
onError : (e) => alart(e)
}
);
console.log(data) // {id : 0 ...}
react-query + axios 조합시 최고의 사용 형태가 아닐까 싶다..!
queryFn 내부에서 직접 axiose response data를 비구조할당한 후 return함으로써,
useQuery의 data에 server에서 반환 받은 응답을 바로 담을 수 있게 된다!
애플리케이션을 개발하다보면 에러를 어디서, 어떻게 다뤄야할지에 대해 많은 고민을 하게 된다.
특히 서버와의 요청-응답이 굉장히 잦은 현 서비스의 특성상...
나도 모르게 별 생각 없이 try-catch문을 모든 비동기 요청에 사용할 때가 있다.
(error log를 찍어야 디버깅이 가능하니)
하지만 이는
1. 해당 에러를 반환하는 주체 파악이 어려움 → 디버깅 ↓
2. 반환된 error의 type 파악이 어려움 → (역시) 디버깅 ↓
3. 불필요한 에러 핸들링 및 전파 코드 중복
과 같은 문제가 있다.
따라서 어디까지 에러를 전파할 것인가, 어디에서 해당 애러를 핸들링할 것인가를 항상 염두하며 코드를 작성할 필요가 있겠다!
'source-code > React' 카테고리의 다른 글
react-dom의 root.render()를 여러 번 호출하면 어떤 일이 생길까? (0) | 2024.01.04 |
---|---|
useQuery data type 지정하기 (feat. 관심사 분리) (1) | 2023.08.16 |
Suspense를 통한 Render-as-you-fetch (0) | 2023.08.16 |
React Hook Form 기반 유효성 버튼 구현 (0) | 2023.08.16 |
react에서 long press 구현하기 (0) | 2023.08.16 |