[react-query] onSuccess와 isSuccess의 차이

숨송·2023년 5월 10일
0

React

목록 보기
2/2
post-thumbnail

문제 상황

@tanstack query v4

리액트 코드 예시

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만 나오는 상태

해결 과정(학습 내용)

What? (뭐야?)

앞에 isLoading, isError 거쳤으니까 그 다음 return은 쿼리 성공 시 아니야?
왜 초깃값인 0-0-0만 나오는 걸까?
onSuccess에서 쿼리 데이터 조작 후 total을 로그에 찍어봐도 정상 결과(0이 아닌 계산된 값)인데, 렌더링은 0-0-0만 나오잖아!

Why Not? (왜 안되냐면)

일반적으로 return하는 JSX 컴포넌트 내에서 arrayQuery의 데이터에만 접근했다면 원하는 대로 렌더링되었을 것이다. 하지만 우리는 데이터를 조작하는 과정을 거쳐야 하고, 자바스크립트에서는 비동기를 고려하지 않을 수 없다.

arrayQuery가 성공하면 두 가지 일이 발생한다.
1. 코드 순서상 자연스레 if문(로딩중)에 걸리지 않고 total 0-0-0을 가진 상태로 return하는 일
2. onSuccess() 콜백 함수에서 열심히 결과를 연산하여 total에 할당하는 일

때문에 우리는 또 2가지의 이유로 0-0-0을 만날 수 밖에 없다.
1. 콜백 함수에서의 total에 결과 연산 값을 할당하는 일이 return 이전의 순서라고 보장할 수 없으므로
2. totalstate가 아니므로 이미 마운트된 컴포넌트에 바뀐 total로 리렌더링 시킬 수 없으므로

How to Fix? (어떻게 고치지?)

내가 해결한 방법은 2가지다.

방법 1. 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로 렌더링이 된다. 처음으로 제대로 정리해보는 onSuccessisSuccess의 차이.

이제까지는 취향 차이인가라는 생각만 해보고, 순간의 작동 느낌에 따라 사용해왔고 그때마다 "아 이렇게 하니까 원하는 대로 되네?"라고 생각만 하고 넘어가서, 비슷한 상황에 맞닥뜨렸을 때 또 다시 "되네? 왜 됐더라?"만 반복할 뿐이었다. (실제로 내 코드엔 "명확한 근거"가 없는 onSuccessisSuccess가 섞여있고, 원하는 대로 동작한건 순전히 우연 혹은 어렴풋이 느낀 감 정도)


방법 2. totaluseState()를 사용하여 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!)

다만 빠르게 구현해야 한다는 핑계로 유튜브 영상 한 개와 예제 코드를 통해서 사용법만 배운 나는 직접 구현하고 여러 에러에 부딪히면서 제대로(?) 배우고 있다.
게다가 리액트의 생명주기에 대해서 제대로 고민하고 이해하기 시작한 것도 얼마 안되어 이제야 정리해본다.. (반성)


더 좋은 방법을 제시해주시거나, 틀린 점을 지적해주신다면 감사하겠습니다!

0개의 댓글