본문 바로가기

개발/React

React-Query(tanstack/query)의 Suspense 옵션을 사용할 때 주의할 점

반응형

모던 프론트엔드에서 대부분의 리액트 코드는 서버와의 통신상태에 따라 View를 나누어 보여주어야하기때문에 아래와 같은 형태로 코드를 작성하는 경우가 빈번하다.

 

/** React */

return (
  {isLoading && <LoadingPage />}
  {apiError && <ErrorPage />}
  {data && <MainPage />}
)
 

 

React에서 제공하는 Suspense와 ErrorBoundary를 조합하여 이러한 형태로 되어있는 코드를 개선하는 방법도 있으나, 이 문서에서는 Suspense만을 이용하여 isLoading에 해당하는 return문을 대체하는 것만을 다룬다.

 

기본적으로 React 17에서의 Suspense는 Data Fetching을 위한 Pending Handler가 아니라, 기존의 워터폴 방식으로 이루어져있는 Render방식을 개선해주는 역할이기때문에, 위의 상황에서 활용하기에는 문제가 있으나, React-Query에서는 Data Fetching시에도 Suspense와 결합할 수 있도록 Suspense 옵션을 제공한다.

 

Suspense옵션을 사용한 Component가 동작하는 방식을 정확히 알아야 일부 엣지케이스에 대응할 수 있는데 이는 다음과 같다.

 

해당 케이스부터 먼저 설명하자면, 기본적으로 React-Query의 useQuery를 사용할 때, onSuccess에 setState를 할 경우 보통의 경우라면 정상적으로 동작한다. 여기서 정상적으로 동작이 의미하는 바는, 해당 setState로 인해 리렌더링을 일어나 화면에 원하는 데이터를 보여줄 수 있다는 것이다.

 

/** suspense가 false인 상황에서는 문제가 없음 */

useTestQuery({
  onSuccess: (data) => {
    setState(~~~)
  }
})

 

다만 이는 Suspense옵션을 사용할 경우에는 setState를 통해 원하는대로 화면을 그려낼 수 없는데, 그 이유를 설명하고자한다.

 

/** Use-Case */

<Suspense fallback={<Loader />}>
  <MainComponent />
</Suspense>

 

React의 Suspense를 사용하는 형태는 대부분 위의 형태와 같은데, 이때 React-Query의 suspense 옵션을 true로 하여 data fetching시에도 fallback UI가 나타나도록 하면 아래의 순서로 동작한다.

 

  1. Suspense mount
  2. MainComponent mount
  3. MainComponent에서 useQuery에 있는 api Call
  4. MainComponent unmount, fallback UI인 Loader mount
  5. api Call Success일 경우, useQuery에 있는 onSuccess 동작
  6. onSuccess 완료 이후 Loader unmount, MainComponent mount

 

setState가 원하는대로 동작하지 않는 이유는 위의 동작순서를 잘 보면 이해할 수 있는데, 이는 setState가 바라보고있는 state최초 mount된 MainComponent의 state이고, 우리가 변경하고자하는 stateapi Call이 성공한 이후 다시 re-mount된 MainComponent의 state이기 때문이다.

이로 인해 suspense 옵션이 켜져있는 상태에서는 onSuccess에서 setState를 하더라도, re-render가 일어나지 않는것처럼 보이는 것이다.

 

그렇다면 위와 같이 작성되어있는 코드는 어떻게 변경해야할까 ?

React-Query의 메인테이너는 최대한 해당 케이스를 지양하나, 꼭 필요하다면 useEffect를 통해 변경을 일으키도록 하는 방법을 권장한다.

 

const { data: resultData } = useTestQuery({
  suspense: true
})

useEffect(() => {
  setState(~~~)
}, [resultData])

 

useEffect에서 해당 액션을 도맡아 할 경우에 원하는대로 동작하는 이유는 최초 mount이자 onSuccess시에 unmount된 컴포넌트에서 호출했던 API call에 대한 result는 해당 Query에 캐싱되어있기때문에, 새로 mount된 컴포넌트에서 setState를 일으키더라도 해당 시점에서 data에 접근하면 캐싱된 데이터를 가지고와 의도한바와 같이 동작시킬 수 있게 된다.

 

 


 

Reference

 

https://github.com/TanStack/query/issues/3784

 

https://ko.reactjs.org/docs/concurrent-mode-suspense.html

반응형

'개발 > React' 카테고리의 다른 글

React 18 - 동시성을 다루기 위한 여정  (0) 2023.12.22
모바일 웹뷰환경에서 영상을 제공하는 여정  (0) 2023.12.10
useTouchNavigation 개발기  (1) 2023.09.10
Why use .jsx  (6) 2021.01.13
Why Virtual DOM?  (0) 2020.04.14