Suspense, ErrorBoundary 비동작 이슈

김동현·2023년 11월 29일
0

이슈 일기

목록 보기
6/6

문제상황
리액트 쿼리의 useQuery를 이용하여 비동기 작업을 수행하고 있었고, useQuery가 반환하는 값인 isLoading을 조건절로 사용해서 로딩 예외처리를 처리하는 대신에 Suspense를 이용하기로 했었다. 그런데 이상하게도 Suspense에서 하위 컴포넌트에서 발생하는 로딩상태를 catch하지 못하는 것이었다.

const MyCardTemplate= () => {
   const { fetchNextPage, isFetchingNextPage, hasNextPage } = useMyCardsQuery({
     tradeStatus,
   })
  // 생략 (무한 스크롤링  관련 코드)
  return (
    <>
      <ErrorBoundary fallback={<div>렌더링 중 문제가 발생했습니다.</div>}>
        <Suspense fallback={<div>로딩중입니다.}>
          <MyCardList />
          <div ref={lastElementRef} />
        </Suspense>
      </ErrorBoundary>
    </>
  )
}
const MyCardList = ({ tradeStatus }: { tradeStatus: any }) => {
  const {
    data,
  } = useMyCardsQuery({
    tradeStatus,
  })
  return (
    <>
      {data?.pages.map(
        ({ data: { cardList } }: GetMyCardListRes, pageIndex) => (
          <Fragment key={pageIndex}>
            {cardList.map((myCard: Card) => (
              <MyCard key={myCard.cardId} card={myCard} />
            ))}
          </Fragment>
        ),
      )}
      <div ref={lastElementRef} />
    </>
  )
}

해당 코드는 MyCardTemplate 컴포넌트 아래에 useMyCardsQuery를 호출하고 hasNextPage, isFetchingNextPage등의 반환값으로 무한스크롤을 처리하고 MyCardList 내부에서는 data를 받기위해 useMyCardsQuery를 다시한번 호출하고 있다.

해결방법
MyCardTemplate에서 useMyCardsQuery를 호출하지 않고 무한스크롤링과 관련된 로직도 MyCardList에서 처리해보기로 하고 다음과 같은 코드를 작성했다.

const MyCardListContent = () => {
  return (
    <>
      <ErrorBoundary fallback={<div>렌더링 중 문제가 발생했습니다.</div>}>
        <Suspense fallback="폴백">
          <MyCardList tradeStatus={tradeStatus} />
          {/* <div ref={lastElementRef} /> */}
        </Suspense>
      </ErrorBoundary>
    </>
  )
}
const MyCardList = ({ tradeStatus }: { tradeStatus: any }) => {
  const { data, fetchNextPage, isFetchingNextPage, hasNextPage } =
    useMyCardsQuery({
      tradeStatus,
    })
  //생략(무한 스크롤링 관련 코드)

  return (
    <>
      {data?.pages.map(
        ({ data: { cardList } }: GetMyCardListRes, pageIndex) => (
          <Fragment key={pageIndex}>
            {cardList.map((myCard: Card) => (
              <MyCard key={myCard.cardId} card={myCard} />
            ))}
          </Fragment>
        ),
      )}
      <div ref={lastElementRef} />
    </>
  )
}	

useMyCardsQuery를 MyCardList 안에서만 호출하니, 이전과 다르게 로딩상태와 에러상태를 캐치하는걸 확인 할 수 있었다.

원인
사실 아직 원인을 파악하지 못했다. 정황상 useMyCardsQuery를 Suspense 바깥에 호출하는 것에 뭔가 문제가 있었던 것 같다.
시간을 내어 이 부분에 대한 원인을 찾아야 할 것 같다.

  • 2023-12-01 추가
    위 오류가 일어난 이유는 MyCardTemplate 컴포넌트를 감싸는 Suspense 컴포넌트가 없기 때문이다.
    Suspense는 설계상 proimese를 상위 컴포넌트에 throw하는 동작을 하고 있는데 MyCardTemplate, MyCardList에서 같은 전역상태를 공유하게 하는 useCardsQuery가 Suspense로 감싸지지 않은 MyCardsTemplate에서 다시한번 호출되고 있기 때문에 Next.js의 error.js가 이를 대신 catch 한 것이었다.

참고 자료 : https://velog.io/@imnotmoon/React-Suspense-ErrorBoundary-%EC%A7%81%EC%A0%91-%EB%A7%8C%EB%93%A4%EA%B8%B0

profile
有備無患 : 미리 대비하고 있으면 걱정할 일이 없다

0개의 댓글