GITHUB
React-Query 살펴보기
BLOGProject
Seohyun
Develop
03 Jul 2023
React-Query 살펴보기

react-query는 react application에서 데이터를 가져오고 캐시하며 업데이트하는 과정 을 간소화 하는 상태 관리 라이브러리입니다. 즉 서버 상태(Server State)를 관리하는 라이브러리입니다.

💡 Client Sate vs. Server State
클라이언트 상태(Client State)와 서버 상 태(Server State)는 전혀 다른 개념이며, 클라이언트에서 소유하며 항상 클라이언트 내에서 사용자 인터렉션에 따라 최신으로 관리됩니다. 이에 반해 서버 상태(Server State)는 Client에서 제어하거나 소유하지 않으며, 데이터베이스에 저장되어 있는상 태라 할 수 있습니다.

장점

자동 백그라운드 재요청, 캐싱 및 오래된 데이터 관리, 에러 처리, 페이지네이션, 무 한 스크롤 등을 간소화 해 구현할 수 있게 하는 장점이 있습니다.

import axios from "axios";
const customFetch = axios.create({
baseURL: "http://localhost:5001/api/tasks",
});
export default customFetch;
// HTTP GET 예시
const fetchTasks = async () => {
try {
const response = await customFetch.get("/");
console.log(response.data);
} catch (error) {
+console.error(error);
}
};
useEffect(() => {
fetchTasks();
}, []);

위와 같이 useEffect로 요청을 설정하는 것과 비교해 react query는 불필요한 재렌더 링이나 네트워크 요청을 최소화 해 boilerplate 코드를 줄이고 성능을 향상시킵니다.



React Query 적용

npm i @tanstack/react-query

react-query를 설치합니다.



import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById("root")).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);

react-query를 사용하려면 QueryClientProvider로 최상단에서 감싸주어야 합니다. 그 후 QueryClient 인스턴스를 App 전체에 연결시켜야 합니다.


useQuery

property

  • isLoading : 쿼리가 현재 로딩 중인지 여부를 나타내는 boolean
  • data : 성공적으로 data fetch한 경우 쿼리로부터 반환된 데이터
  • error : 쿼리 도중 발생한 에러
  • isError : 쿼리의 결과가 오류인지 여부를 나타내는 boolean

method

  • refetch : 쿼리 데이터를 수동으로 refetch 하도록 trigger 하는 함수
  • remove : 캐시에서 특정 쿼리를 제거 가능한 함수


import { useQuery } from "@tanstack/react-query";
const result = useQuery({
queryKey: ["tasks"],
queryFn: () => customFetch.get("/"),
});
console.log(result);

useQuery는 3개의 인자를 받습니다. 첫 번째 인자는 queryKey, 두 번째 인자는 queryFn로 필수입니다. 그러나 세 번째 인자 options는 선택적입니다. queryKey는 데이터 캐싱을 관리하는 고유한 키입니다. 배열 표기법을 사용합니다. 애플리케이션 전체에서 쿼리를 공유하며 내부적으로 네트워크를 다시 요청하고 캐시합 니다. queryFn은 Promise를 반환하는 함수를 넣어야 합니다. useQuery 훅이 호출될 때 실행되는 콜백 함수입니다. 세 번째 인자는 다양한 옵션이 있는데 statleTime, cacheTime, retry 등이 있습니다. useQuery 공식문서를 통해자 세히 살펴볼 수 있습니다. 위 예시에서는 queryKey와 queryFn만 사용해 보겠습니다.


useMutation

property

  • isLoading : 현재 뮤테이션인지 로딩 중인지 여부를 나타내는 boolean
  • isSuccess : 뮤테이션이 성공적으로 완료되었는지 여부를 나태는 boolean
  • isError : 뮤테이션 중에 오류가 발생했는지 여부를 나타내는 boolean
  • data : 뮤테이션에 의해 반환된 데이터

method

  • mutate : 뮤테이션을 실행하기 위해 호출할 수 있는 함수 (첫 번 째 인자 - 필요한 변수를 가진 객체를 전달 가능)
  • onSuccess : 뮤테이션이 성공적으로 완료됐을 때 호출될 콜백 함 수
  • onError : 뮤테이션 중 오류가 발생했을 떄 호출될 콜백 함수
  • reset : 뮤테이션을 초기 상태로 리셋하는 함수


const { mutate: createTask, isLoading } = useMutation({
mutationFn: (taskTitle) => customFetch.post("/", { title: taskTitle }),
onSuccess: () => {
// 성공했을 때
},
onError: () => {
// 실패했을 때
},
});

서버의 data를 post, patch, put, delete와 같이 수정 또는 삭제 하고자 할 때 useMutation을 사용합니다. useMutation은 비동기적으로 data를 수정하고, data를 업 데이트 해 UI를 재렌더링 합니다. useMutation hook의 반환 값인 mutation 객체는 mutation과 상호 작용하고 결과를 처리하는 데 사용할 수 있는 여러 프로퍼티와 메서 드가 있습니다.


Query Invalidation

Query Invalidation은 react-query에서 캐시된 data를 무효화 합니다. data를 수정, 삭제했을 때 해당 데이터를 refetch 해서 캐시를 업데이트 해야 합니다. react-query 는 Query Invalidation을 자동으로 처리하며 useMutation hook을 사용해 data를 수정, 삭제하면 해당 data를 가져오는 모든 query를 무효화합니다.


Code

Create Task

const { mutate: createTask, isLoading } = useMutation({
mutationFn: (taskTitle) => customFetch.post("/", { title: taskTitle }),
});
const handleSubmit = (e) => {
e.preventDefault();
createTask(newItemName);
};

data를 create 할 때에는 useMutation hook을 사용합니다. useMutation의 반환 값인 mutation 객체의 mutate 메서드를 이용해 요청 함수를 호출합니다.


결과

POST


Get Task

const Items = () => {
const { isLoading, data, error, isError } = useQuery({
queryKey: ["tasks"],
queryFn: async () => {
const { data } = await customFetch.get("/");
return data;
},
});
// 쿼리가 현재 로딩 중이면
if (isLoading) {
return <p style={{ marginTop: "1rem " }}>Loading...</p>;
}
// 쿼리 도중 에러가 발생한 경우
if (error) {
return <p style={{ marginTop: "1rem " }}>{error.message}</p>;
}
return (
<div className="items">
{data.taskList.map((item) => {
return <SingleItem key={item.id} item={item} />;
})}
</div>
);
};
export default Items;

react-query를 통해 서버에서 data를 GET할 때에는 useQuery hook을 사용합니다.

useQuery hook은 객체를 반환합니다. 위 예시에서는 isLoading, data, error, isError 프로퍼티를 구조 분해 할당을 통해 사용했습니다.


결과

GET GET 요청을 통해 data가 잘 fetch 된 것을 thunder client를 통해 확인할 수 있습니다 .


Edit Task

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
// useQueryClient hook을 통해 queryClient 객체 생성
const queryClient = useQueryClient();
const { mutate: editTask } = useMutation({
mutationFn: ({ taskId, isDone }) => {
return customFetch.patch(`/${taskId}`, { isDone });
},
onSuccess: () => {
// 'tasks' 키가 있는 모든 쿼리 무효화
queryClient.invalidateQueries({ queryKey: ["tasks"] });
},
onError: (err) => {
console.log(err);
},
});

data를 수정(edit)할 때에는 useMutation hook을 사용합니다. useMutation의 반환 값 인 mutation 객체의 mutate 메서드를 이용해 요청 함수를 호출합니다. mutate는 onSuccess 메서드를 통해 성공 했을 시 response data를 핸들링할 수 있습니다. 위 코드에서는 data가 수정되었으므로 캐시 data를 최신화 해야 합니다. 이 경우 쿼리가 오래되었다고 판단하고 다시 refetch 하기 위해 invalidateQuries() 메서드를 사용 합니다. onError 메서드를 통해 실패 했을 시 response data를 핸들링합니다.


결과

PATCH

Delete Task

const { mutate: deleteTask, isLoading } = useMutation({
mutationFn: (taskId) => {
return customFetch.delete(`/${taskId}`);
},
onSuccess: () => {
// 'tasks' 키가 있는 모든 쿼리 무효화
queryClient.invalidateQueries({ queryKey: ["tasks"] });
},
onError: (err) => {
console.log(err);
},
});

data 삭제도 수정도 마찬가지로 mutate 메서드를 실행 후 성공했을 때(onSuccess) queryClient.invalidateQueries()를 통해 쿼리의 캐시된 data를 업데이트 시켜줍니다.


결과

DEL

© 2024 Park Seohyun