function TotalInfo() {
const total = {
currentCnt: 0,
goalCnt: 0,
percent: 0,
};
const arrayQuery = useQuery({
queryKey: ["example"],
queryFn: getArray,
onSuccess: (dataArray) => {
// dataArray를 조작해서 total 만들기
}
});
if (arrayQuery.isLoading) return null;
if (arrayQuery.isError) console.log(arrayQuery.error);
return (
<div>
<span>현재 : {currenCnt}</span>
<span>목표 : {goalCnt}</span>
<span>달성도 : {percent}</span>
</div>
);
}
현재 횟수와 목표 횟수, 그리고 달성도를 서버에서 불러온 데이터를 연산해서 사용을 해야 하는 상황이다. 서버에서 받아온 arrayQuery
의 데이터를 그대로 사용할 수 없고, 이 결과로 얻은 array를 map()
이나 filter()
혹은 length
를 이용하여 원하는 값을 얻어야 한다.
하지만 이 상태에서는 결과가 현재:0 목표:0 달성도:0
, 즉 초기 할당값 0-0-0만 나오는 상태
앞에 isLoading
, isError
거쳤으니까 그 다음 return
은 쿼리 성공 시 아니야?
왜 초깃값인 0-0-0만 나오는 걸까?
onSuccess
에서 쿼리 데이터 조작 후 total
을 로그에 찍어봐도 정상 결과(0이 아닌 계산된 값)인데, 렌더링은 0-0-0만 나오잖아!
일반적으로 return
하는 JSX 컴포넌트 내에서 arrayQuery
의 데이터에만 접근했다면 원하는 대로 렌더링되었을 것이다. 하지만 우리는 데이터를 조작하는 과정을 거쳐야 하고, 자바스크립트에서는 비동기를 고려하지 않을 수 없다.
arrayQuery
가 성공하면 두 가지 일이 발생한다.
1. 코드 순서상 자연스레 if
문(로딩중)에 걸리지 않고 total
0-0-0을 가진 상태로 return
하는 일
2. onSuccess()
콜백 함수에서 열심히 결과를 연산하여 total
에 할당하는 일
때문에 우리는 또 2가지의 이유로 0-0-0을 만날 수 밖에 없다.
1. 콜백 함수에서의 total
에 결과 연산 값을 할당하는 일이 return
이전의 순서라고 보장할 수 없으므로
2. total
은 state
가 아니므로 이미 마운트된 컴포넌트에 바뀐 total
로 리렌더링 시킬 수 없으므로
내가 해결한 방법은 2가지다.
onSuccess
의 연산을 if (arrayQuery.isSuccess)
내부에서 하는 방식function TotalInfo() {
const total = {
currentCnt: 0,
goalCnt: 0,
percent: 0,
};
const arrayQuery = useQuery({
queryKey: ["example"],
queryFn: getArray,
});
if (arrayQuery.isLoading) return null;
if (arrayQuery.isError) console.log(arrayQuery.error);
if (arrayQuery.isSuccess) {
// dataArray를 조작해서 total 만들기
}
return (
<div>
<span>현재 : {currenCnt}</span>
<span>목표 : {goalCnt}</span>
<span>달성도 : {percent}</span>
</div>
);
}
이렇게 하면 동기적으로 작동되므로, 차례로 조건문을 거쳐 연산이 끝난 total
로 렌더링이 된다. 처음으로 제대로 정리해보는 onSuccess
와 isSuccess
의 차이.
이제까지는 취향 차이인가라는 생각만 해보고, 순간의 작동 느낌에 따라 사용해왔고 그때마다 "아 이렇게 하니까 원하는 대로 되네?"라고 생각만 하고 넘어가서, 비슷한 상황에 맞닥뜨렸을 때 또 다시 "되네? 왜 됐더라?"만 반복할 뿐이었다. (실제로 내 코드엔 "명확한 근거"가 없는 onSuccess
와 isSuccess
가 섞여있고, 원하는 대로 동작한건 순전히 우연 혹은 어렴풋이 느낀 감 정도)
total
을 useState()
를 사용하여 onSuccess
에서 상태 관리하기이 경우 too many rerender 와 같은 무한 콜백에 빠지는 에러가 발생할 수 있으니 setState()
는 onSuccess
에서만 사용해야 한다.
(게다가 hook은 조건문에서 사용하지 말라고 리액트에서 강조하고 있다)
( + Why? ) if (arrayQuery.isSuccess)
내부에 사용할 경우 렌더링 과정중에 setState()
로 인한 상태 변화로 리렌더링 트리거 -> 다시 if (arrayQuery.isSuccess)
로 돌아와 setState()
-> 리렌더링 트리거 -> 무한 반복 ... 이 되므로
방법 2에서 발전시켜보고자 복잡한 연산과 여러 방식의 setState()
를 하다 보니, hook에 대한 rule 위반 에러도 만나게 되어 결국 방법 1로 돌아갔지만 다시금 hook에 대한 사용을 상기시킬 수 있었다.
이 부분도 제대로 학습하고 정리할 수 있는 기회가 생기면 좋겠다!
useQuery()
내부의onSuccess
는 성공 시 진행하는 콜백 함수이므로 렌더링 이후의 side effect가 되는 것이고,if (query.isSuccess)
를 사용한다면 렌더링 과정(컴포넌트 return 전)의 함수를 만드는 것이다.
아마 이런 내용은 tanstack의 공식 문서에 잘 정리되어 있을 것이다. 더 정확한 용어와 정확한 근거와 적절한 예시코드로 정리되어 있을 지도.. (RTFM!)
다만 빠르게 구현해야 한다는 핑계로 유튜브 영상 한 개와 예제 코드를 통해서 사용법만 배운 나는 직접 구현하고 여러 에러에 부딪히면서 제대로(?) 배우고 있다.
게다가 리액트의 생명주기에 대해서 제대로 고민하고 이해하기 시작한 것도 얼마 안되어 이제야 정리해본다.. (반성)
더 좋은 방법을 제시해주시거나, 틀린 점을 지적해주신다면 감사하겠습니다!