오늘은 지난 포스트에서 설명했듯 useQuery와 useMutation에 대해 작성해 볼 것이다.
Queries
쿼리는 고유 키와 연결된 비동기 데이터 소스에 대한 선언적 의존이다. 쿼리는 서버에서 데이터를 가져오기 위해 Promise 기반 메서드(예: GET 및 POST)를 사용한다. 데이터를 수정하는 메서드에는 Mutations를 사용하는 것이 좋다.
서버에서 데이터를 가져올 때 -> useQuery
데이터를 수정할 때 -> useMutaion
useQuery 사용법
queryKey : 쿼리의 고유 키 , 캐시를 관리하기 위한 키 값으로 배열 형태로 사용
queryFn : promise 를 반환하는 함수
import { useQuery } from '@tanstack/react-query'
function App() {
const info = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
}
제공된 고유 키는 내부적으로 재패칭, 캐싱, 쿼리 공유 등에 사용된다.
useQuery가 반환하는 쿼리 결과 객체는 쿼리에 필요한 모든 정보를 포함한다. (데이터, 에러 , isLoading, isFetching ... )
우리가 흔히 데이터를 요청할 때 데이터를 요청하고 기다린 후 데이터가 성공적으로 요청되거나 , 에러가 발생하거나 하는 상태가 존재한다. useQuery를 사용하면 이러한 상태들을 관리하는데 용이하다.
useQuery 결과 상태
- isPending 또는 status === 'pending' - 아직 데이터가 없음
- isError 또는 status === 'error' - 오류 발생
- isSuccess 또는 status === 'success' - 성공적으로 데이터가 있음
또한 각 상태에 따라 추가 정보를 얻을 수 있다.
- isFetching - 어떤 상태에서도 쿼리가 패칭 중일 때
- error - isError 상태일 때 오류 정보
- data - isSuccess 상태일 때 데이터
function Todos() {
const { isPending, isError, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
});
if (isPending) {
return <span>Loading...</span>;
}
if (isError) {
return <span>Error: {error.message}</span>;
}
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
function Todos() {
const { status, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
});
if (status === 'pending') {
return <span>Loading...</span>;
}
if (status === 'error') {
return <span>Error: {error.message}</span>;
}
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
이런식으로 사용하게 되면 데이터의 상태에 따라 완전히 데이터를 받아오기 전 사용자에게 로딩창을 띄워주거나 에러 발생 시 에러메세지를 띄워줄 수 있다.
또한 추가적으로 status 필드 외에 fetchStatus 속성도 있다.
- fetchStatus === 'fetching' - 현재 패칭 중
- fetchStatus === 'paused' - 패칭이 필요하지만 중단됨 (네트워크 모드 가이드 참조)
- fetchStatus === 'idle' - 현재 아무 것도 하지 않음
왜 두 가지 상태가 필요한가?
백그라운드 재패칭과 stale-while-revalidate 로직은 모든 상태와 fetchStatus 조합을 가능하게 한다.
예를 들어
- 성공 상태의 쿼리는 보통 idle fetchStatus에 있지만, 백그라운드 재패칭 중이라면 fetching일 수도 있다.
- 데이터가 없는 쿼리는 보통 pending status와 fetching fetchStatus에 있지만, 네트워크 연결이 없으면 paused일 수도 있다.
따라서 쿼리는 실제로 데이터를 패칭하지 않고도 pending 상태일 수 있다.
- status는 데이터에 대한 정보를 제공한다 -> 데이터가 있는지 여부
- fetchStatus는 queryFn에 대한 정보를 제공한다 -> 실행 중인지 여부
Mutations
queries와 달리 mutations는 데이터를 생성/업데이트/삭제하거나 서버 측 효과를 수행하는 데 사용된다. (CUD에 해당하는 기능)
mutations는 쿼리키는 필요하지 않고 queryFn만 사용한다.
import { useMutation } from '@tanstack/react-query';
import axios from 'axios';
function App() {
const mutation = useMutation({
mutationFn: (newTodo) => {
return axios.post('/todos', newTodo);
},
});
return (
<div>
{mutation.isPending ? (
'Adding todo...'
) : (
<>
{mutation.isError ? (
<div>An error occurred: {mutation.error.message}</div>
) : null}
{mutation.isSuccess ? <div>Todo added!</div> : null}
<button
onClick={() => {
mutation.mutate({ id: new Date(), title: 'Do Laundry' });
}}
>
Create Todo
</button>
</>
)}
</div>
);
}
useMutaion 결과 상태
- isIdle 또는 status === 'idle' - 현재 대기 중이거나 초기 상태
- isPending 또는 status === 'pending' - 현재 실행 중
- isError 또는 status === 'error' - 오류 발생
- isSuccess 또는 status === 'success' - 성공적으로 완료되어 데이터가 있음
상태에 따른 추가 정보:
- error - 오류 상태일 때 오류 정보 제공
- data - 성공 상태일 때 데이터 제공
useMutaion 사용법
기본적인 사용방법은 위에서 설명했고 아래는 특별히 주의할 점에 대해 작성해볼 것이다.
1. mutate 함수는 비동기 함수이므로 React 16 및 이전 버전 의 이벤트 콜백에서 직접 사용할 수 없다 .onSubmit 에서 이벤트에 액세스해야 하는 경우 mutate를 다른 함수로 래핑해야 합니다 . 이는 React 이벤트 풀링 때문
// This will not work in React 16 and earlier
const CreateTodo = () => {
const mutation = useMutation({
mutationFn: (event) => {
event.preventDefault()
return fetch('/api', new FormData(event.target))
},
})
return <form onSubmit={mutation.mutate}>...</form>
}
// This will work
const CreateTodo = () => {
const mutation = useMutation({
mutationFn: (formData) => {
return fetch('/api', formData)
},
})
const onSubmit = (event) => {
event.preventDefault()
mutation.mutate(new FormData(event.target))
}
return <form onSubmit={onSubmit}>...</form>
}
2. Mutation 상태 재설정
요청의 오류나 데이터를 지워야 할 때는 reset 함수를 사용하면 된다.
const CreateTodo = () => {
const [title, setTitle] = useState('')
const mutation = useMutation({ mutationFn: createTodo })
const onCreateTodo = (e) => {
e.preventDefault()
mutation.mutate({ title })
}
return (
<form onSubmit={onCreateTodo}>
{mutation.error && (
<h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
)}
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<br />
<button type="submit">Create Todo</button>
</form>
)
}
3. Mutation 재시도
TanStack Query는 mutation 오류 시 재시도하지 않지만 retry 옵션으로 가능하게 할 수 있다.
const mutation = useMutation({
mutationFn: addTodo,
retry: 3,
});
retry : 3 을 설정해둔단면 동작 실패 시 3번 재시도 한다는 의미이다.
4. Promise 반환
성공 시 해결되거나 오류가 발생하는 promise을 얻으려면 mutate 대신 mutateAsync를 사용하면 된다.
const mutation = useMutation({ mutationFn: addTodo })
try {
const todo = await mutation.mutateAsync(todo)
console.log(todo)
} catch (error) {
console.error(error)
} finally {
console.log('done')
}
오늘은 이렇게 useQuery와 useMutation의 차이점과 사용방법에 대해 알아보았다.
각 훅들이 제공하는 유용한 옵션들도 있으니 함께 알아보고 사용하면 좋을 것 같다 !!
https://tanstack.com/query/latest/docs/framework/react/reference/useQuery
https://tanstack.com/query/latest/docs/framework/react/reference/useMutation
다음 포스트에서는 진행중인 프로젝트에 리액트 쿼리를 적용한 결과에 대해 작성해 볼 것이다.
'리액트' 카테고리의 다른 글
TanStack Query (React Query) 공식문서 정복하기 (1) (1) | 2024.06.10 |
---|---|
[리액트] 웹 배포 시 다크모드가 설정되어 CSS가 깨지는 경우엔 ? (0) | 2024.02.21 |
솔로프로젝트 - MaMaMeMo (아이디어 기획 및 디자인) (0) | 2023.09.11 |
[블로깅 챌린지] 리액트로 간단한 컴포넌트 구현하기 (0) | 2023.05.03 |
[블로깅 챌린지] React Context 정복하기 (0) | 2023.04.26 |