일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- context.api
- virtaullist
- TypeScript
- 에세이
- CRA
- 리팩토링
- 회고
- MicroFrontEnd
- provider 패턴
- CustomHook
- Webworker
- 아키텍처
- 클린코드
- 자바스크립트
- 웹워커
- 오블완
- sharedworker
- 리액트
- 합성 컴포넌트
- JavaScript
- radixui
- 이것저것
- vite
- Web
- server component
- MFA
- react
- 티스토리챌린지
- frontend
- 프론트엔드
- Today
- Total
Lighthouse of FE biginner
[React] react-query prefetch 본문
들어가며
사이드 프로젝트를 진행중 매출을 등록할 때 필드가 너무 늦게 뜨거나 어떤 경우 데이터가 안나온다는 피드백을 받았습니다.
이번 포스팅에서 해당 케이스를 react-query의 prefetch를 통해 해결한 과정을 남겨보겠습니다.
위 사진에서 확인할 수 있듯이 매출 화면에 접근할 때 마다 매번 필터 데이터를 호출하고 있습니다.
데이터의 성격
문제가 되는 데이터들은 매출을 등록할 때, 등록된 매출을 필터링할 때 사용하는 사용자 정의 메타데이터 입니다.
즉 자주 변경되지 않는 데이터이며, 사용자에 의해 데이터의 변형이 거의 일어나지 않는 데이터 입니다.
기존 구현에서는 해당 데이터를 필요로 하는 곳에서 react-query 를 통해 서버 상태로 관리하고 있었습니다.
즉 해당 데이터를 사용하는 화면에 접근할 때 마다 결제 유형, 서비스 유형, 방문 유형 데이터를 호출하고 있었습니다.
해당 데이터들은 자주 변경이 되지 않고, 사용자의 요청에 의해서만 변하는 데이터입니다.
따라서 사용자가 앱에 접근했을 때 prefetch를 통해 query를 캐싱한 후 실제 데이터를 사용하는 페이지에서 데이터의 호출 없이 화면에 바로 display 하는 것으로 결정했습니다.
여기서 동시성 문제로, 만약 사용자가 아닌 로그인 한 제 3자가 데이터를 변경했을 경우엔 사용자가 stale한 상태의 데이터를 바라볼 수 있지 않을까 고민했지만 현재 구현한 웹 애플리케이션은 1:1 방식으로 설계가 되어있어 사용자의 세션이 탈취당하지 않은 이상 해당 데이터를 변경할 수 없다 라는 결론이 도출됐습니다.
마지막으로 유저가 실제 데이터를 변경할 경우 invalidateQueries 메서드를 활용해 캐싱된 query를 강제로 stale 하게 만들었습니다.
구현
먼저 같은 종류의 query는 같은 옵션을 공유해야 합니다.
prefetchQuery에 넣어줄 쿼리 옵션 객체는 useVisitTypes의 옵션과 동일한 옵션이여야 합니다.
tanstack/react-query 패키지에서 제공하는 UseQueryOptions 타입을 사용해 옵션 객체의 타입을 만들고, 패키지의 queryOptions메서드에 options 객체를 넣어주며 옵션 객체를 만듭니다.
해당 options 객체를 useQuery를 래핑한 커스텀 훅에서 호출해주고, 쿼리의 옵션으로 사용합니다.
type VisitTypesQueryOptions = Omit<UseQueryOptions<VisitType[]>, "queryKey">;
export const visitTypesQueryOptions = (options?: VisitTypesQueryOptions) =>
queryOptions({
...options,
staleTime: FILTER_STALE_TIME,
queryKey: [KEYS.settings.visitTypes],
queryFn: getVisitTypes,
});
export const useVisitTypes = (
options?: Omit<UseQueryOptions<VisitType[]>, "queryKey" | "queryFn">
) => {
const queryOptions = visitTypesQueryOptions(options);
return useQuery<VisitType[]>({
...queryOptions,
});
};
prefetchQuery에서도 위 옵션 객체를 사용합니다.
import { useQueryClient } from "@tanstack/react-query";
import {
visitTypesQueryOptions,
} from "@/queries/settings";
import { useCallback, useEffect, useRef } from "react";
const usePrefetchFilters = () => {
const isMount = useRef(false);
const queryClient = useQueryClient();
const visitTypesOptions = visitTypesQueryOptions();
const prefetchFilters = useCallback(async () => {
await queryClient.prefetchQuery({
...visitTypesOptions,
});
}, [
queryClient,
visitTypesOptions,
]);
useEffect(() => {
if (isMount.current) return;
isMount.current = true;
prefetchFilters();
}, [prefetchFilters]);
};
export default usePrefetchFilters;
prefetch 로직을 담고 있는 커스텀 훅에서 옵션 객체를 불러오고, 해당 옵션을 활용해 query를 prefetch를 합니다.
이때 isMount 라는 ref를 활용해 mount 된 이후에는 prefetchFilter을 하지 않도록 막아줍니다.
변경 이후 매출 페이지에 접근할 때 매번 필터 데이터를 호출하지 않고 캐싱된 데이터를 사용하고 있습니다.
마치며
tanstack/react-query의 강력한 기능 중 하나는 캐싱 전략 입니다. 캐싱을 잘 활용하면 사용자에게 더 좋은 경험을 제공할 수 있습니다.
그리고 데이터의 성격을 잘 고민해보면 더 좋은 UX를 만들수 있지 않을까라는 생각도 듭니다.
'[WEB] 프론트엔드' 카테고리의 다른 글
[React] 제어 컴포넌트, 비제어 컴포넌트, 그리고 폼 다루기 (0) | 2025.06.01 |
---|---|
Vite의 사전 번들링, HMR (0) | 2025.05.23 |
[MFA] Micro App의 관심사 공유 (0) | 2025.02.13 |
[React] 합성(Composition) 컴포넌트 (2) | 2025.01.01 |
[트러블슈팅] WebWorker 빌드 시 worker 파일이 포함이 안되는 문제 (2) | 2024.10.19 |