yeahzzz
archive
yeahzzz
전체 방문자
오늘
어제
  • 분류 전체보기 (164)
    • Language (41)
      • Python (12)
      • JAVA (21)
      • C&C++ (8)
    • Algorithms (25)
      • programmers (9)
      • study log (16)
    • Problems & Solutions (14)
    • Major (29)
      • Data Structure & Algorithm (14)
      • Linux(Ubuntu) (9)
      • Security (2)
      • Linear Algebra (4)
    • FE (44)
      • Web(HTML5, CSS, JS) (5)
      • React & TS (26)
      • 코딩일기 (10)
    • BE (1)
      • Node.js (1)
    • Pytorch (8)
    • Server (2)

블로그 메뉴

  • 홈

공지사항

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
yeahzzz

archive

[TS] React Query와 Suspense, ErrorBoundary 함께 사용하기
FE/React & TS

[TS] React Query와 Suspense, ErrorBoundary 함께 사용하기

2024. 7. 14. 21:12

처음에는, 전역에서 suspense 옵션을 index.tsx의 queryClient에 전역으로 설정하려고 했다.

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
    },
  },
});

하지만 아래와 같은 에러가 발생.

🚫 개체 리터럴은 알려진 속성만 지정할 수 있으며 'OmitKeyof<QueryObserverOptions<unknown, Error, unknown, unknown, QueryKey, never>, "suspense" | "queryKey", "strictly">' 형식에 'suspense'이(가) 없습니다.ts(2353) (property) suspense: boolean

아차차.. 리액트 쿼리 v5에서는 suspense: boolean 옵션은 제거되고 useSuspenseQuery, useSuspenseInfiniteQuery와 useSuspenseQueries를 사용한다.

내 프로젝트의 package.json 파일을 살펴보면 역시 v5. "@tanstack/react-query": "^5.51.1",

버전을 다운그레이드 하는 것보다 최신 문법을 따르기로 했다.

https://www.moonkorea.dev/React-TanStack-Query-v5-살펴보기-(리액트쿼리)

useSuspenseQuery 사용해서 데이터 가져오기

const { data } = useSuspenseQuery<GoodsData[]>({
  queryKey: ['goodsList', filterOption],
  queryFn: () => fetchGoodsList(filterOption),
});

suspense hook은 로딩과 에러 상태를 Suspense와 ErrorBoundary가 처리하기 때문에 status가 언제나 success인 data 값을 반환한다.

FallBack 작성하기

로딩 완료되기 전까지 사용자에게 보여줄 fallBack을 작성해주고,

import styled from '@emotion/styled';

//테마 카테고리
export const ThemeCategoryLoading = () => <Loader>카테고리를 불러오는 중입니다..</Loader>;
//실시간 선물 랭킹 상품 목록
export const GoodsRankingListLoading = () => <Loader>상품 목록을 불러오는 중입니다..</Loader>;
//themePage
export const ThemePageLoading = () => <Loader>테마별 상품 페이지를 불러오는 중입니다..</Loader>;

const Loader = styled.div({
  display: 'flex',
  flexDirection: 'column',
  alignItems: 'center',
  justifyContent: 'center',
  textAlign: 'center',
  padding: '40px',
});

부모(상위) 컴포넌트에 감싸주었다. 지금은 일단 텍스트로 간단히 작성했지만 스피닝도 구현해보면 재밌을 듯

<Suspense fallback={<GoodsRankingListLoading />}>
  <GoodsRankingSection />
</Suspense>

이건 혁명이야..!!

이전과 비교해보면 코드가 훨씬 간단해졌다.. Suspense FallBack을 사용해서 외부에서 로딩 상태를 핸들링 할 수 있어 넘 편하고 혁명 그자체. 유지보수하기도 훨씬 좋아보인다.

const fetchGoodsList = async (filterOption: RankingFilterOption) => {
  const params = {
    targetType: filterOption.targetType,
    rankType: filterOption.rankType,
  };
  const { data } = await fetchData<GetGoodsDataResponse>(API_ENDPOINT.RANKING, params);
  return data.products;
};

export const GoodsRankingSection = () => {
  const [filterOption, setFilterOption] = useState<RankingFilterOption>({
    targetType: 'ALL',
    rankType: 'MANY_WISH',
  });

  const { data } = useSuspenseQuery<GoodsData[]>({
    queryKey: ['goodsList', filterOption],
    queryFn: () => fetchGoodsList(filterOption),
  });

  return (
    <Wrapper>
      <Container>
        <Title>실시간 급상승 선물랭킹</Title>
        <GoodsRankingFilter filterOption={filterOption} onFilterOptionChange={setFilterOption} />
        <GoodsRankingList goodsList={data} />;
      </Container>
    </Wrapper>
  );
};

아직 한발 남았다. ErrorBoundary

구현한 메인 페이지는 여러 섹션으로 나뉘어 있다.

일부 영역에서 데이터를 불러오지 못했을 때 에러 핸들링을 해주고, 다시 시도가 가능하도록 버튼을 추가해주었다.

const RetryErrorBoundary = ({ children }: PropsWithChildren<unknown>) => {
  const { reset } = useQueryErrorResetBoundary();

  return (
    <ErrorBoundary
      onReset={reset}
      fallbackRender={({ error, resetErrorBoundary }: FallbackProps) => (
        <Wrapper>
          <h2>😭에러가 발생했습니다😭</h2>
          <h4 style={{ color: 'red' }}> {error.message} </h4>
          <Button
            theme="kakao"
            onClick={() => resetErrorBoundary()}
            style={{ width: '100px', margin: '16px 0' }}
          >
            다시 시도하기
          </Button>
        </Wrapper>
      )}
    >
      {children}
    </ErrorBoundary>
  );
};

이 UI는 화면 전체를 덮는 것이 아닌, 에러가 난 영역에서만 표시된다. 해당 글을 많이 참고했다 :D

상위 컴포넌트에서 ErrorBoundary와 Suspense로 감싸주기

<RetryErrorBoundary>
  <Suspense fallback={<ThemeCategoryLoading />}>
    <ThemeCategorySection />
  </Suspense>
</RetryErrorBoundary>

이렇게 컴포넌트 외부에서 편리하게 에러와 로딩 상태를 핸들링할 수 있다.

코드도 예전보다 훠어어어얼씬 깔끔해졌다. (너무좋다…)

const fetchThemeList = async () => {
  const { data } = await fetchData<GetThemeListResponse>(API_ENDPOINT.THEMES);
  return data.themes;
};

export const ThemeCategorySection = () => {
  const { data } = useSuspenseQuery<ThemeList[]>({
    queryKey: ['themeList'],
    queryFn: () => fetchThemeList(),
  });

  return (
    <Wrapper>
      <Container>
        {...}
      </Container>
    </Wrapper>
  );
};
저작자표시

'FE > React & TS' 카테고리의 다른 글

[TS] onSubmit와 formData를 사용하여 form 유효성 검사하기  (0) 2024.07.18
[TS] useInfiniteQuery와 useInView 훅으로 무한스크롤 구현하기  (0) 2024.07.14
프론트엔드에서 효과적으로 비동기 처리하기(feat. TanStack Query & Suspense)  (0) 2024.07.13
[TS] 타입 단언(as Type)과 타입 선언(: Type)을 비교해보자  (0) 2024.07.13
[React] formData 객체 생성  (0) 2024.05.06
    'FE/React & TS' 카테고리의 다른 글
    • [TS] onSubmit와 formData를 사용하여 form 유효성 검사하기
    • [TS] useInfiniteQuery와 useInView 훅으로 무한스크롤 구현하기
    • 프론트엔드에서 효과적으로 비동기 처리하기(feat. TanStack Query & Suspense)
    • [TS] 타입 단언(as Type)과 타입 선언(: Type)을 비교해보자
    yeahzzz
    yeahzzz

    티스토리툴바