useQuery를 사용하며 생긴 이슈를 작성해본다.
구현중인 기능
1. 쿼리스트링을 useQuery의 props 로 보내서 데이터를 받는다.
2. useResult(props)로 받은 data를 다른 useResultBestChmi, useResultWorstChmi에 props로 보내어 데이터를 받는다.
3. 이동하기 버튼을 누를 시에 다른 값의 쿼리스트링을 넣어 해당 페이지를 호출한다.
1. useQuery 데이터 undefined
//api.ts
export const getResult = async (key: string) => {
const {data} = await clientApi.get(`/api/result/${key}`)
return data;
};
//query.ts
export const useResult = (key: string, options?: UseQueryOptions<{ data: IResult }, AxiosError>) => {
return useQuery<{ data: IResult }, AxiosError>(['result', key], () => API.getResult(key), options)
}
//result.tsx
const { data: result } = useResult(key)
익숙하지 않아 주먹구구식으로 무지성 복사를 하여... 지난 프로젝트에서 잘 되고 있던 아이를 가지고 와서 사용하였다.
하지만 이상하게도 api.ts에서는 data를 콘솔로 찍어보았을 때 값이 잘 출력되고 있는데도
result.tsx에서 result는 도무지 값이 나오질 않고 있었다.
지난 프로젝트에서의 api 데이터 타입은 이러했다.
type Data={
"data":{
key:'',
title: '',
name:''
}[]
}
하지만 이번에 내가 작성한 api의 데이터 타입은 이러하다.
type Data = {
key: string,
title: string,
name: string,
}
헷갈렸던 것은, api에서 가지고 있는 데이터가 똑같이 배열로 되어있다는 것이었다.
하지만 실제 api에서 보내고 있는 형태에 따라 프론트에서 내가 받아야 할 데이터는,
- 이전 프로젝트 : json 안의 json배열
- 이번 프로젝트 : json 이었다.
그러므로 이미 api.ts에서 json안의 데이터를 받았는데,
result.tsx에서 그 데이터 안에 없는 json 데이터 안 데이터를 받으려니 undefined 였던 것이다.
그래서 api.ts의 소스를 이렇게 수정하였다.
export const getResult = async (key: string) => {
const data = await clientApi.get(`/api/result/${key}`)
return data;
};
json 형태의 데이터를 그대로 받아다가 리턴하였고, useResult를 호출한 result.tsx 에서 json 안의 data 를 받았더니 해결!
2. useQuery 의 리턴값으로 다른 useQuery 실행하기.
const { data: result } = useResult(key)
const { data: bestResult } = useResult(result?.data.chemi.best)
const { data: worstResult } = useResult(result?.data.chemi.worst)
처음에는 이렇게 작성하였다. 이 소스의 문제는 두 가지이다.
1. 첫줄이 완료된 후 아래 두 줄의 소스가 동기적으로 실행되어야 한다.
2. 같은 useQuery hook을 사용하고 있다. (querykey가 같다.)
먼저, useQuery는 비동기방식으로 실행되기 때문에 첫 줄이 완료되지 않은 채 없는 result 값으로 다음 줄을 실행하려니 당연하게 에러가 발생했다. 동기식으로 실행하기 위해서는 useQuery의 enabled 옵션을 사용하면 된다. enabled는 default value는 true이고, 해당 훅의 활성화 여부를 설정하는 옵션이다.
enabled에 변수를 넣어 해당 변수에 값이 비어있지 않으면 실행되도록 한다.
const { data: result } = useResult(key)
const bestChemi = result?.data.chemi.best
const worstChemi = result?.data.chemi.worst
const { data: bestResult } = useResult(bestChemi, {
enabled: !!bestChemi,
})
const { data: worstResult } = useResult(worstChemi, {
enabled: !!worstChemi,
})
//여기서 !!bestChmi의 의미
//1. 조건절 안에 변수만 넣거나 논리연산자 !를 넣으면 해당 변수는 undefined가 아닐 시 true를 리턴한다.
//2. if(bestChmi)는 bestChmi에 할당된 값이 있다면 if(true)를 의미한다.
//3. 또한 !bestChmi 는 false 가 되며, !!bestChmi는 true가 될 것이다.
//4. 그러므로 변수 앞에 !!를 붙이면 not*not의 의미로 해당 변수에 할당된 값이 있을 때 true를 리턴한다.
의문이 생긴 부분이 있다. enabled에 직접 !!result?.data.chemi.best 를 넣어주었을 때에는 원하는 대로 잘 실행되지 않았다. 공식문서에 예시로 변수에 값을 넣어주고 있어서 그대로 수정하니 잘 실행된다. 이 부분은 고민이 더 필요하다.
다음!
어찌 result의 undefined로 bestResult 값을 가져오는 부분의 오류는 수정되었으나, bestResult를 거쳐 worstResult 값으로 result, bestResult, worstResult 가 동시에 수정되었다.
그 이유는... 캐시에 있다.
react query는 캐시 방식으로 데이터를 가져오는데, 같은 query key를 호출하니 해당 query key의 데이터를 수정하여 모두 같은 값을 가져오고 있는 것이었다. 각각 다른 변수에 저장될 것이라고 생각한 나의 잘못된 판단이었다.
querykey가 다른 세개의 훅을 작성해주어야 했다.
//queries.ts
//수정 전
export const useResult = (key: string, options?: UseQueryOptions<{ data: IResult }, AxiosError>) => {
return useQuery<{ data: IResult }, AxiosError>(['result'], () => API.getResult(key), options)
}
//수정 후
export const useResult = (key: string, options?: UseQueryOptions<{ data: IResult }, AxiosError>) => {
return useQuery<{ data: IResult }, AxiosError>(['result'], () => API.getResult(key), options)
}
export const useResultBestChemi = (key: string, options?: UseQueryOptions<{ data: IResult }, AxiosError>) => {
return useQuery<{ data: IResult }, AxiosError>(['best'], () => API.getResult(key), options)
}
export const useResultWorstChemi = (key: string, options?: UseQueryOptions<{ data: IResult }, AxiosError>) => {
return useQuery<{ data: IResult }, AxiosError>(['worst'], () => API.getResult(key), options)
}
그리고 각자 다른 훅을 호출하도록 작성하고 나니 원하는대로 각각의 데이터가 잘 나왔다.
해결!
3. 다른 값의 쿼리스트링으로 재호출 시 데이터가 변경되지 않는 이슈
가장 잘 맞는 유형과 정반대의 유형을 클릭하면 해당 유형의 결과 페이지로 이동하게 하고 싶었다.
쿼리스트링은 제대로 수정되고 있지만 query가 다시 실행되지 않고 있었다. 이미 가져온 데이터를 그대로 가지고 오고 있었다.
react query는 분명히 props가 바뀔 때 re-fetch 하여 데이터를 다시 가지고 오는 것으로 알고 있었는데, 새로고침 버튼을 누르지 않는 이상 렌더링이 다시 되더라도 query는 재실행되지 않았다.
공식 github에 나와 같은 고민을 가진 분이 질문을 남겨주셔서 덕분에 해결 방법을 찾을 수 있었다.
//query key에 props를 넣어주면 props 값이 바뀔 때마다 refetch된다.
export const useResult = (key: string, options?: UseQueryOptions<{ data: IResult }, AxiosError>) => {
return useQuery<{ data: IResult }, AxiosError>(['result', key], () => API.getResult(key), options)
}
이렇게 해결.
정리
const Result = () => {
//라우터 설정
const router = useRouter()
//useQuery의 props를 url paramMap에서 가져온다.
//useResult의 props는 string형식으로 지정되어있다.
//router.query는 string[] | string 타입이므로 정확하게 string 타입만 props로 보낼 수 있도록 분기처리한다.
const key = (router.query?.key && typeof router.query?.key === 'string') ? router.query?.key : ''
//useQuery
const { data: result } = useResult(key)
//result 값을 받아온 후에 실행해야 하는 hook
const bestChmi = result?.data.chemi.best
const worstChmi = result?.data.chemi.worst
const { data: bestResult } = useResultBestChmi(bestChmi, {
enabled: !!bestChmi, //bestChmi 값이 있을 경우에 활성화된다.
})
const { data: worstResult } = useResultWorstChmi(worstChmi, {
enabled: !!worstChmi,
})
return(
<div onClick={()=>router.push(`/result?key=${result?data.chemi.best}`)}>이동하기</div>
)
}
//query.ts
export const useResult = (key: string, options?: UseQueryOptions<{ data: IResult }, AxiosError>) => {
return useQuery<{ data: IResult }, AxiosError>(['result', key], () => API.getResult(key), options)
}
export const useResultBestChemi = (key: string, options?: UseQueryOptions<{ data: IResult }, AxiosError>) => {
return useQuery<{ data: IResult }, AxiosError>(['best', key], () => API.getResult(key), options)
}
export const useResultWorstChemi = (key: string, options?: UseQueryOptions<{ data: IResult }, AxiosError>) => {
return useQuery<{ data: IResult }, AxiosError>(['worst', key], () => API.getResult(key), options)
}
공부를 해보더라도 실제 사용해보지 않으면 개념적으로 알고 있던 사실도 잘 다가오지 않는다. 프로젝트에 적용해보며 react query와 더 가까워진 것 같다.
'주말에 쓰는 개발일기 > react' 카테고리의 다른 글
[React Query] 적용기 (0) | 2022.08.17 |
---|