// Tanstack Query
- 서버 상태 관리를 용이하게 해주는 라이브러리
- React Query로 알려져 있었다.
- 리액트 앱 내부에서 서버에 HTTP 요청을 간편하게 보낼 수 있게 해준다.
- HTTP 요청을 직접 전송하는 로직이 내장된 것이 아니라, 요청을 관리하는 로직을 제공해준다.
- 공식 문서: https://tanstack.com/query/latest/docs/framework/react/overview
- 서버에서 가져온 데이터를 cache 처리하고 메모리에 저장해서 필요할 때 다시 사용할 수 있다.
1. 설치
npm install @tanstack/react-query
2. Tanstack query 사용할 컴포넌트 감싸기
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
}
- queryClient를 공유하면, queryKey를 이용해 원하는 query를 만료시키고 refetch 시킬 수 있다.
queryClient.invalidateQueries({ queryKey: [''], exact:true });
- exact: true로 설정하면 queryKey가 정확히 일치하는 것만 만료시킬 수 있다.
// 데이터 가져오기 (useQuery)
- useQuery hook은 자체적으로 HTTP request를 전송하여 response를 받는다.
import { useQuery } from '@tanstack/react-query';
const { data, isPending, isError, error } = useQuery({
queryKey: [],
queryFn: query함수,
});
- data : response data를 받는다.
- isPending: request 진행 상태를 받는다.
- isError: 오류 response 여부를 받는다.
- error: 발생한 오류의 정보를 받는다.
- queryKey: request로 생성된 data의 cache 처리에 활용한다.
> 값은 배열인데, 요소가 여러 개일 수도 있고, 문자열이 아니어도 된다. - queryFn : 실제 request를 전송할 때 실행할 코드를 정의한다.
import {
createBrowserRouter,
Navigate,
RouterProvider,
} from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
}
export default App;
import LoadingIndicator from '../UI/LoadingIndicator.jsx';
import ErrorBlock from '../UI/ErrorBlock.jsx';
import EventItem from './EventItem.jsx';
import { useQuery } from '@tanstack/react-query';
import { fetchEvents } from '../../util/http.js';
export default function NewEventsSection() {
const { data, isPending, isError, error } = useQuery({
queryKey: ['events'],
queryFn: fetchEvents,
});
let content;
if (isPending) {
content = <LoadingIndicator />;
}
if (error) {
content = (
<ErrorBlock
title="An error occurred"
message={error.info?.message || 'Failed to fetch events.'}
/>
);
}
if (data) {
content = (
<ul className="events-list">
{data.map((event) => (
<li key={event.id}>
<EventItem event={event} />
</li>
))}
</ul>
);
}
return (
<section className="content-section" id="new-events-section">
<header>
<h2>Recently added events</h2>
</header>
{content}
</section>
);
}
import axios from 'axios';
export async function fetchEvents() {
try {
const { data } = await axios.get('http://localhost:3000/events');
return data.events;
} catch (error) {
if (error.response) {
const customError = new Error(
'An error occurred while fetching the events',
);
customError.code = error.response.status;
customError.info = error.response.data;
throw customError;
} else if (error.request) {
throw new Error('No response was received');
} else {
throw new Error('Error in setting up the request');
}
}
}
// Cache 처리
- request를 통해 받은 response data를 cache 처리하고, 나중에 동일한 queryKey 를 가진 또다른 useQuery가 실행되면 이 data를 재사용한다.
import { useQuery } from '@tanstack/react-query';
const { data, isPending, isError, error } = useQuery({
queryKey: [],
queryFn: query함수,
staleTime: ms,
gcTime: ms,
});
- staleTime: 업데이트된 데이터를 가져오기 위한 request를 전송하기 전 기다릴 시간
> 기다리는 동안은 cache에 있는 data를 사용한다. - gcTime: data와 cache를 보관할 시간(garbage collector)
// 동적 query
- query key를 동적으로 사용하면, 같은 query를 사용하더라도 key마다 각기 다른 data를 cache 처리할 수 있다.
const { data, isPending, isError, error } = useQuery({
queryKey: ['', { 동적 value }],
queryFn: ({ signal }) => 비동기함수({ signal, 동적 value }),
enabled: Boolean값
});
- 요청 취소 정보인 signal도 queryFn의 매개변수로 받아서 request의 config로 넘겨주어야 동적 query를 사용하지 않는 query도 문제없이 작동한다.
- enabled: 쿼리 활성화/비활성화를 설정할 수 있다.
- false 시 비활성화, true 시 활성화
- 쿼리가 비활성화 되면 상태를 대기 중으로 처리하기 때문에 isPending이 true가 된다.
> isPending이 아닌 isLoading을 사용하면 쿼리가 비활성화 되더라도 true가 되지 않는다.
// 데이터 보내기 (useMutation)
- useQuery hook은 자체적으로 HTTP request를 전송하여 response를 받는다.
import { useMutation } from '@tanstack/react-query';
const { mutate, isPending, isError, error } = useMutation({
mutationFn: 함수,
onSuccess: () => {},
onMutate:async (data) => {
const 기존data = queryClient.getQueryData([update하려는queryKey]);
await queryClient.cancelQueries({ queryKey: [update하려는queryKey]});
queryClient.setQueryData([update하려는queryKey], data);
return {기존data};
}
onError: (error, data, context) => {
queryClient.setQueryData([update하려는queryKey], context.기존data);
},
});
mutate(data);
- mutate : 해당 컴포넌트 어디서든 mutationFn을 호출해서 요청을 전송할 수 있게 해준다.
- isPending: request 진행 상태를 받는다.
- isError: 오류 response 여부를 받는다.
- error: 발생한 오류의 정보를 받는다.
- mutationKey: request로 생성된 data의 cache 처리에 활용한다.
> queryKey와 다르게 mutation은 response data를 cache 처리하지 않기 때문에 필수X - mutationFn : 실제 request를 전송할 때 실행할 코드를 정의한다.
- onSuccess : request 성공 시 처리할 내용을 정의한다.
- onMutate : mutate를 호출하는 즉시 실행된다.
> react-query가 자동으로 mutate 함수에 넘긴 data를 받아온다. - onError: onMutate의 반환값을 context로 받아온다.
- onSettled: 성공 여부와 관계 없이 mutationFn이 완료될 때마다 호출된다.
import { Link, useNavigate } from 'react-router-dom';
import Modal from '../UI/Modal.jsx';
import EventForm from './EventForm.jsx';
import { useMutation } from '@tanstack/react-query';
import { createNewEvent, queryClient } from '../../util/http.js';
import ErrorBlock from '../UI/ErrorBlock.jsx';
export default function NewEvent() {
const navigate = useNavigate();
const { mutate, isPending, isError, error } = useMutation({
mutationFn: createNewEvent,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['events'] });
navigate('/events');
},
});
function handleSubmit(formData) {
mutate({ event: formData });
}
return (
<Modal onClose={() => navigate('../')}>
<EventForm onSubmit={handleSubmit}>
{isPending && 'Submitting...'}
{!isPending && (
<>
<Link to="../" className="button-text">
Cancel
</Link>
<button type="submit" className="button">
Create
</button>
</>
)}
</EventForm>
{isError && (
<ErrorBlock
title="Failed to create event"
message={
error.info?.message ||
'Failed to create event. Please check your inputs and try agin later'
}
/>
)}
</Modal>
);
}
export async function createNewEvent(eventData) {
try {
const { data } = await axios.post(
'http://localhost:3000/events',
eventData,
);
return data.event;
} catch (error) {
if (error.response) {
const customError = new Error(
'An error occurred while creating the event',
);
customError.code = error.response.status;
customError.info = error.response.data;
throw customError;
} else if (error.request) {
throw new Error('No response was received');
} else {
throw new Error('Error in setting up the request');
}
}
}
※ react-query v3 이상에서 useQuery는 isPending 대신 isLoading을 주로 사용한다.
※ react-query v5 이상에서 useMutation은 isLoading 대신 isPending을 사용한다.
'클라이언트 > React' 카테고리의 다른 글
[리액트(React)] 유닛 테스트(Unit Test) (8) | 2024.03.07 |
---|---|
[리액트(React)] 애니메이션(Animation) (0) | 2024.03.06 |
[리액트(React)] 배포(Deploying) (0) | 2024.02.23 |
[리액트(React)] 인증(Authentication) (0) | 2024.02.22 |
[리액트(React)] Router - useFetcher (0) | 2024.02.21 |