useQuery의 속성인 isLoading, isError를 통해 데이터 상태를 UI로 구현하여 사용자 경험을 개선시킬 수 있었다. 하지만, query 요청이 필요한 컴포넌트마다 useQuery의 속성을 사용해야하므로 이는 비효율적이라는 생각이 들었다.
다행히 React Query에서는 useIsFetching
과 useIsMutating
이라는 기능을 통해 전역적으로 데이터 사태에 대해 관찰할 수 있다.
useIsFetching
은 애플리케이션이 백그라운드에서 로드하거나 가져오는 query 수
를 반환
useIsMutating
은 애플리케이션이 가져오는 mutate 수
를 반환
따라서, 데이터를 요청하거나 업데이트할 경우에 데이터가 로드되는 동안 사용자에게 보여줄 로딩 UI를 출력할 수 있다.
또한 데이터 요청에 대한 응답 결과를 사용자에게 보여주면 더욱 사용자 경험이 개선될 것이다. 예를 들어, 데이터를 수정,등록,삭제 등의 사용자 요청에 대한 응답을 보여주는 Alert UI를 사용하여 데이터가 성공적으로 응답했는지, 실패했는지를 구분할 수 있을 것이다.
해당 기능은 onSuccess
, onError
속성을 통해 쉽게 구현 가능하다.
(물론, suspense를 이용할 수도 있고, 다른 방법도 존재하겠쥬.)
✅ spinner 컴포넌트
export function Loading(): ReactElement {
const isFetching = useIsFetching();
const isMutating = useIsMutating();
const display = isFetching || isMutating ? "inherit" : "none";
return (
<Spinner
thickness="4px"
speed="0.65s"
emptyColor="gray.200"
color="blue.500"
size="xl"
role="status"
position="fixed"
zIndex="9999"
top="50%"
left="50%"
transform="translate(-50%, -50%)"
display={display}
>
<Text>Loading...</Text>
</Spinner>
);
}
✅ _app.js
const App = ({ Component, pageProps }) => {
..
return (
<QueryClientProvider client={getClient()}>
<Hydrate state={pageProps.dehydratedState}>
<Loading />
<Component {...pageProps} />
</Hydrate>
</QueryClientProvider>
);
};
✅ Custom Toast
// hooks/ useCustomToast.ts
import { ToastId, useToast, UseToastOptions } from "@chakra-ui/react";
interface UseCustomToast {
(options?: UseToastOptions | undefined): string | number | undefined;
close: (id: ToastId) => void;
isActive: (id: ToastId) => boolean | undefined;
}
export function useCustomToast(): UseCustomToast {
return useToast({
isClosable: true,
variant: "subtle",
position: "bottom",
});
}
✅ onSucess, onError
const toast = useToast();
// hooks/useDeleteMessage.ts
const { mutate: onDelete } = useMutation(
(id: string) => fetcher(DELETE_MESSAGE, { id, userId }),
{
onSuccess: () => {
...
toast({
title: "Content has been deleted.",
status: "success",
isClosable: true,
});
},
}
);
// hooks/useCreateMessage.ts
const { mutate: onCreate } = useMutation(
({ text }: { text: string }) => fetcher(CREATE_MESSAGE, { text, userId }),
{
onSuccess: () => {
...
toast({
title: "Content has been posted.",
status: "success",
isClosable: true,
});
},
onError: () => {
toast({
title: "You must enter the content.",
status: "warning",
isClosable: true,
});
},
}
);
// hooks/useUpdateMessage.ts
const { mutate: onUpdate } = useMutation(
({ text, id }: { text: string; id?: string }) => fetcher(UPDATE_MESSAGE, { text, id, userId }),
{
onSuccess: () => {
...
toast({
title: "Content has been updated.",
status: "success",
isClosable: true,
});
},
}
);
✅ 실제 구현 화면