### 참고문헌
- [공식 홈페이지](https://tanstack.com/query/latest)
- [공식 레퍼런스 : Quick Start](https://tanstack.com/query/latest/docs/framework/react/quick-start)
## 설치
```
$ npm i @tanstack/react-query
$ npm i -D @tanstack/eslint-plugin-query
```
## 기본 세팅
- (주의사항) app router에서
- root layout은 반드시 src/ 폴더의 root에 위치해야함. `()` 폴더를 사용하면 경로무시 폴더를 만들수 있는데 거기다가 layout 파일을 위치시키면 안됨
- react-query가 적용된 페이지는 "use client"를 붙여줘야함.
## Queries
- [공식문서 : Queries](https://tanstack.com/query/latest/docs/framework/react/guides/queries)
- 조회용 쿼리를 호출하는 경우 `useQuery` hook을 사용한다. 수정이 필요한 쿼리에도 사용할 수 있지만 그러한 경우 Mutations 를 사용한다.
- useQuery를 사용하려면 key(queryKey)와 data 또는 error를 리턴하는 함수(queryFn)가 필요하다.
```
import { useQuery } from '@tanstack/react-query'
function App() {
const result = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
}
```
- 위 예제에서 result는 다음의 유용한 변수들을 가지고 있다.
- data : 쿼리 결과 데이터
- status : 쿼리 상태
- isPending : 쿼리 결과 데이터가 아직 없을 때. 쿼리는 호출 중 status === 'pending'
- isError : 에러인지 아닌지 체크 status === 'error'
- isSuccess : 쿼리 결과가 성공했는지 체크 status === 'success'
- isFetching : 위 3개의 조건은 내부적으로 'status'를 체크하지만 isFetching은 상태와 상관없이 쿼리가 실행중('fetching')이면 'true' 이다.
- 구조분해 할당을 통해 아래와 같이 코딩이 가능하다.
```
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> )
}
```
## Mutations
- [공식문서 : Mutations](https://tanstack.com/query/latest/docs/framework/react/guides/mutations)
- 데이터의 추가, 수정, 삭제 시에는 `useMutation`을 사용한다.
- 데이터의 상태이상이 발생하므로 이에 대한 처리는 [[#Query Invalidation]] 항목을 을 참고
```
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>
)
}
```
- 'Queries'에서와 마찬가지로 위 예제에서 'mutation' 상수는 다음의 프로퍼티를 가지고 있다.
- isIdle : status === 'idle' , 리프레시, 리셋 등에 의한 초기 실행직전 상태이다.
- isPending
- isError
- isSuccess
- error
- data
- mutation은 쿼리와 다르게 `mutation.mutate()` 라는 메소드를 호출해야 실행이 된다. 아래 두 예제를 보자
```
// 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>
}
```
- use mutation 과 mutate의 차이
```
useMutation({
mutationFn: addTodo,
onSuccess: (data, error, variables, context) => {
// Will be called 3 times
},
})
const todos = ['Todo 1', 'Todo 2', 'Todo 3']
todos.forEach((todo) => {
mutate(todo, {
onSuccess: (data, error, variables, context) => {
// Will execute only once, for the last mutation (Todo 3),
// regardless which mutation resolves first
},
})
})
```
- 리셋하기
```
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>
)
}
```
- promises
```
const mutation = useMutation({ mutationFn: addTodo })
try {
const todo = await mutation.mutateAsync(todo)
console.log(todo)
} catch (error) {
console.error(error)
} finally {
console.log('done')
}
```
- retry
```
const mutation = useMutation({
mutationFn: addTodo,
retry: 3,
})
```
- Persist mutations
- mutations은 필요할 때 재실행 될 수 있도록 저장될 수 있다. 이 기능은 hydrate function을 이용할 수 있다.
```
const queryClient = new QueryClient()
// Define the "addTodo" mutation
queryClient.setMutationDefaults(['addTodo'], {
mutationFn: addTodo,
onMutate: async (variables) => {
// Cancel current queries for the todos list
await queryClient.cancelQueries({ queryKey: ['todos'] })
// Create optimistic todo
const optimisticTodo = { id: uuid(), title: variables.title }
// Add optimistic todo to todos list
queryClient.setQueryData(['todos'], (old) => [...old, optimisticTodo])
// Return context with the optimistic todo
return { optimisticTodo }
},
onSuccess: (result, variables, context) => {
// Replace optimistic todo in the todos list with the result
queryClient.setQueryData(['todos'], (old) =>
old.map((todo) =>
todo.id === context.optimisticTodo.id ? result : todo,
),
)
},
onError: (error, variables, context) => {
// Remove optimistic todo from the todos list
queryClient.setQueryData(['todos'], (old) =>
old.filter((todo) => todo.id !== context.optimisticTodo.id),
)
},
retry: 3,
})
// Start mutation in some component:
const mutation = useMutation({ mutationKey: ['addTodo'] })
mutation.mutate({ title: 'title' })
// If the mutation has been paused because the device is for example offline,
// Then the paused mutation can be dehydrated when the application quits:
const state = dehydrate(queryClient)
// The mutation can then be hydrated again when the application is started:
hydrate(queryClient, state)
// Resume the paused mutations:
queryClient.resumePausedMutations()
```
## Query Invalidation
- [공식문서 : Query Invalidation](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation)
- 조회된 데이터가 변경되었을 때 변경된 데이터를 재 조회하는 것이 변경된 데이터임을 인지한 사용자에게 좋을 것이다. query invalidation을 이용하면 변경이후 데이터를 재 조회할 수 있게 해준다.
- 아래 두 예제에서 queryKey부분을 잘 살펴보자.
```
import { useQuery, useQueryClient } from '@tanstack/react-query'
// Get QueryClient from the context
const queryClient = useQueryClient()
queryClient.invalidateQueries({ queryKey: ['todos'] })
// Both queries below will be invalidated
const todoListQuery = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
const todoListQuery = useQuery({
queryKey: ['todos', { page: 1 }],
queryFn: fetchTodoList,
})
```
```
queryClient.invalidateQueries({
predicate: (query) =>
query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10,
})
// The query below will be invalidated
const todoListQuery = useQuery({
queryKey: ['todos', { version: 20 }],
queryFn: fetchTodoList,
})
// The query below will be invalidated
const todoListQuery = useQuery({
queryKey: ['todos', { version: 10 }],
queryFn: fetchTodoList,
})
// However, the following query below will NOT be invalidated
const todoListQuery = useQuery({
queryKey: ['todos', { version: 5 }],
queryFn: fetchTodoList,
})
```
## Devtools
- [공식 문서 : Devtools](https://tanstack.com/query/latest/docs/framework/react/devtools)
- Floating Mode
```
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* The rest of your application */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
```
- in production
```
import * as React from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { Example } from './Example'
const queryClient = new QueryClient()
const ReactQueryDevtoolsProduction = React.lazy(() =>
import('@tanstack/react-query-devtools/build/modern/production.js').then(
(d) => ({
default: d.ReactQueryDevtools,
}),
),
)
function App() {
const [showDevtools, setShowDevtools] = React.useState(false)
React.useEffect(() => {
// @ts-expect-error
window.toggleDevtools = () => setShowDevtools((old) => !old)
}, [])
return (
<QueryClientProvider client={queryClient}>
<Example />
<ReactQueryDevtools initialIsOpen />
{showDevtools && (
<React.Suspense fallback={null}>
<ReactQueryDevtoolsProduction />
</React.Suspense>
)}
</QueryClientProvider>
)
}
export default App
```
댓글
댓글 쓰기