※ 본 강의에서의 react query는 3버전으로 4버전과는 버전 차이가 있어 코드가 다를 수 있습니다.
select
optionselect
option the best way to do this?select
function (React Query는 셀렉트 함수를 삼중 등호로 비교하며)useCallback
for anonymous function) (따라서 셀렉트 함수에는 안정적인 함수가 필요합니다. 매번 바뀌는 익명 함수, 즉 삼중 등호로 비교하는 함수는 실패합니다. 익명함수를 안정적인 함수로 만들고 싶을 때는 React의 useCallback함수를 사용하면 됩니다.)useQuery
의 select
옵션을 사용하면 쿼리 함수가 반환하는 데이터를 변환할 수 있습니다.
// useAppointments.ts
...
interface UseAppointments {
appointments: AppointmentDateMap;
monthYear: MonthYear;
updateMonthYear: (monthIncrement: number) => void;
showAll: boolean; // calendar.tsx 파일에서 showAll 이 boolean으로 나타낼 수 있도록 설정합니다.
setShowAll: Dispatch<SetStateAction<boolean>>;
}
...
const [showAll, setShowAll] = useState(false); // showAll의 useState
const { user } = useUser();
const selectFn = useCallback((data) => getAvailableAppointments(data, user), [
user, // 로그인하는 사용자가 바뀌거나 사용자가 로그아웃할 때마다 이 함수를 변경해야 합니다.
]);
// 익명함수를 안정적인 함수로 만들기 위해 useCallback 함수를 사용합니다.
...
const { data: appointments = fallback } = useQuery(
[queryKeys.appointments, monthYear.year, monthYear.month],
() => getAppointments(monthYear.year, monthYear.month),
{
select: showAll ? undefined : selectFn, // 기존 useQuery함수에서 select option을
}, // 추가 하여 showAll이 참이면 undefined가 나타나고 거짓이면 selectFn 함수가 나타나게 합니다.
);
...
// useStaff.ts 변경 전
...
export function useStaff(): UseStaff {
const [filter, setFilter] = useState('all'); // all이 기본으로 설정되고 이 상태의 현재 값고 setter 는 이 훅을 실행하는 이라면 누구에게나 공개 되어 있습니다.
// 그렇게 해야 이 값에 접근하고 업데이트 할 수 있습니다.
const fallback = [];
const { data: staff = fallback } = useQuery(queryKeys.staff, getStaff);
return { staff, filter, setFilter };
}
// AllStaff.tsx
...
const { staff, filter, setFilter } = useStaff();
...
// utils.ts
import type { Staff } from '../../../../shared/types';
export function filterByTreatment(
staff: Staff[], // 거르지 않은 staff 데이터
treatmentName: string, // & 서비스명
): Staff[] {
return staff.filter((person) =>
person.treatmentNames
.map((t) => t.toLowerCase())
.includes(treatmentName.toLowerCase()),
);
}
// 내가 시도 한 코드
export function useStaff(): UseStaff {
const [filter, setFilter] = useState('all');
const filterFn = useCallback((data) => filterByTreatment(data, filter), [
filter,
]);
const fallback = [];
const { data: staff = fallback } = useQuery(
[queryKeys.staff, filter],
getStaff,
{
select: filterFn,
},
);
return { staff, filter, setFilter };
}
// 실제 코드
export function useStaff(): UseStaff {
const [filter, setFilter] = useState('all');
const selectFn = useCallback(
(unfilteredStaff) => filterByTreatment(unfilteredStaff, filter),
[filter],
);
const fallback = [];
const { data: staff = fallback } = useQuery(queryKeys.staff, getStaff, {
select: filter !== 'all' ? selectFn : undefined,
});
return { staff, filter, setFilter };
}
useStaff 훅에서는 selectFn에 unfilteredStaff를 가져와서 filterByTreatment로 전달하라고 정의를 내릴 겁니다.
useCallback으로 불러온 익명함수는 안정적이고 재처리가 필요한 데이터가 있는지를 검토하는 React Query의 3중 동등 검사를 통과할 것입니다.
useCallback을 위한 의존성 배열은 filter 상태 값이 될 것입니다.
useQuery를 위한 select 옵션은 all 인지 여부와는 무관하게 filter에 의존합니다. filter가 all이라고 설정되면 filter 함수가 필요 없고 여과되지 않은 데이터를 얻게 됩니다.
filter가 all이 아닌 경우에는 데이터를 거를 겁니다.
실제 코드에서 filterFn = selectFn
은 같게 만들었으나 select 옵션의 설정 값이 달랐다. 내가 직접 실했을 때 all 버튼을 눌렀을 경우 모든 스태프가 나오지 않아서 고민을 했었는데 실제 코드의
select: filter !== 'all' ? selectFn : undefined,
로 설정 해주니 all 라디오 버튼을 클릭했을 때 모든 스태프가 나오게 되었다. 내가 만든 프로젝트가 아니다보니 다른 컴포넌트의 코드 이해가 부족해서 틀리지 않았나 싶다.
refetchInterval
has expired (리페칭 간격이 지난 경우도 해당되는데 )refetchOnMount
, refetchOnWindowFocus
, refetchOnReconnect
, refetchInterval
(앞의 3개는 boolean, 마지막은 밀리초 단위의 시간)refetch
function in useQuery
return object (리페칭을 명령할 수도 있어서 useQuery를 쓰면 객체를 반환합니다. 데이터나 오류 같은 것인데 refetch 함수를 반환하기도 합니다. 불러오려는 데이터가 있을 때 호출하는 방법입니다.)// 기존 코드
...
export function useTreatments(): Treatment[] {
const fallback = [];
const { data = fallback } = useQuery(queryKeys.treatments, getTreatments);
return data;
}
export function usePrefetchTreatments(): void {
const queryClient = useQueryClient();
queryClient.prefetchQuery(queryKeys.treatments, getTreatments);
}
useQuery
에 대한 옵션이 없어 다른 메뉴로 넘어갔을 때마다 업데이트가 진행됩니다.// useQuery option 적용 코드
...
export function useTreatments(): Treatment[] {
const fallback = [];
const { data = fallback } = useQuery(queryKeys.treatments, getTreatments, {
staleTime: 600000, // 10minutes
cacheTime: 900000, // 15minutes (doesn't make sense for staleTime to exceed cacheTime)
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
});
return data;
}
...
staleTime
은 10분, cacheTime
은 15분, refetchOnMount: false
, refetchOnWindowFocus: false
, refetchOnReconnect: false
로 설정하여 Treatment와 Staff의 화면 전환이 되어 업데이트 시간이 변경되지 않습니다. 설정한 10분이 지나야 자동으로 업데이트 됩니다.// prefetchQuery option 적용 코드
...
export function usePrefetchTreatments(): void {
const queryClient = useQueryClient();
queryClient.prefetchQuery(queryKeys.treatments, getTreatments, {
staleTime: 600000, // 10minutes
cacheTime: 900000, // 15minutes (doesn't make sense for staleTime to exceed cacheTime)
});
}
...
// queryClient.ts
...
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: queryErrorHandler,
staleTime: 600000, // 10 minutes
cacheTime: 900000, // 15 minutes,
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
},
},
});
// useTreatments.ts
export function useTreatments(): Treatment[] {
const fallback = [];
const { data = fallback } = useQuery(queryKeys.treatments, getTreatments);
return data;
}
export function usePrefetchTreatments(): void {
const queryClient = useQueryClient();
queryClient.prefetchQuery(queryKeys.treatments, getTreatments);
}
treatment와 staff는 실시간으로 데이터가 업데이트 될 필요가 없어 queryClient에 옵션을 설정해주어도 상관 없으나 appointments는 실시간으로 정보가 업데이트 되는 것이 중요합니다. 그래야 사용자들이 예약을 할 수 있습니다.
게다가 appointments는 사용자 활동이 없을 때에도 서버에 변경이 이루어져야 했습니다.
// useAppointments.ts
...
const fallback = {};
const { data: appointments = fallback } = useQuery(
[queryKeys.appointments, monthYear.year, monthYear.month],
() => getAppointments(monthYear.year, monthYear.month),
{
select: showAll ? undefined : selectFn,
staleTime: 0,
cacheTime: 3000000, // 5 minutes,
refetchOnMount: true,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
},
);
...
위처럼 적용하면 리페칭은 프리페칭에 적용되지 않지만 staleTime과 cacheTime은 프리페칭에 적용 됩니다. 이 부분을 따로 분리해서 새로운 commonsOptions 함수를 만들어 prefetch에 적용합니다.
...
const commonOptions = { // => 에러 발생
staleTime: 0,
cacheTime: 300000, // 5 minutes,
};
...
const queryClient = useQueryClient();
useEffect(() => {
const nextMonthYear = getNewMonthYear(monthYear, 1);
queryClient.prefetchQuery(
[queryKeys.appointments, nextMonthYear.year, nextMonthYear.month],
() => getAppointments(nextMonthYear.year, nextMonthYear.month),
commonOptions,
);
}, [queryClient, monthYear, commonOptions]);
...
const fallback = {};
const { data: appointments = fallback } = useQuery(
[queryKeys.appointments, monthYear.year, monthYear.month],
() => getAppointments(monthYear.year, monthYear.month),
{
select: showAll ? undefined : selectFn,
...commonOptions,
refetchOnMount: true,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
},
);
...
함수안에 적용하면 commonOptions
함수에 에러가 발생하여 함수 바깥에서 선언해주고 useEffect의 의존성 배열에서 제거 합니다.
...
const commonOptions = {
staleTime: 0,
cacheTime: 300000, // 5 minutes,
};
...
export function useAppointments(): UseAppointments {
...
const queryClient = useQueryClient();
useEffect(() => {
const nextMonthYear = getNewMonthYear(monthYear, 1);
queryClient.prefetchQuery(
[queryKeys.appointments, nextMonthYear.year, nextMonthYear.month],
() => getAppointments(nextMonthYear.year, nextMonthYear.month),
commonOptions,
);
}, [queryClient, monthYear]);
...
const fallback = {};
const { data: appointments = fallback } = useQuery(
[queryKeys.appointments, monthYear.year, monthYear.month],
() => getAppointments(monthYear.year, monthYear.month),
{
select: showAll ? undefined : selectFn,
...commonOptions,
refetchOnMount: true,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
},
);
...
staleTime
, cacheTime
, refetchOn*
refetchInterval
option to useQuery
userAppointments
?// useAppointments.ts
...
const fallback = {};
const { data: appointments = fallback } = useQuery(
[queryKeys.appointments, monthYear.year, monthYear.month],
() => getAppointments(monthYear.year, monthYear.month),
{
select: showAll ? undefined : selectFn,
...commonOptions,
refetchOnMount: true,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
refetchInterval: 1000, // every second; not recommended for production
},
);
...
select
option for filteringreference
https://www.udemy.com/course/learn-react-query/
https://tkdodo.eu/blog/react-query-data-transformations
https://tanstack.com/query/v4/docs/guides/important-defaults?from=reactQueryV3&original=https://react-query-v3.tanstack.com/guides/important-defaults