처음에는, 전역에서 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 |