리액트 쿼리는 누락된 데이터 가져오는 라이브러리로 설명되지만, 리액트 애플리케이션에서 서버 가져오기, 캐싱, 동기화 및 업데이트를 쉽게 만들어준다
대부분의 상태 관리 라이브러리는 상태 작업에 적합하지만 비동기 또는 서버 상태에는 적합하지 않다. 그 이유는 서버 상태가 완전히 다르기 때문이다.
애플리케이션에서 서버 상태의 특성을 파악하면 더 많은 문제가 발생한다
위의 문제들을 리액트 쿼리는 해결할 수 있는 서버 상태 관리 라이브러리 중 하나이다
리액트 쿼리를 씀으로써 보일러플레이트를 제거하고 빠르고 잠재적으로 대역폭을 절약하고 메모리 성능을 높이는데 도움이 된다
$ npm i react-query
또는 $ yarn add react-query
A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. A query can be used with any Promise based method (including GET and POST methods) to fetch data from a server. If your method modifies data on the server, we recommend using Mutations instead.
쿼리는 고유 키에 연결된 데이터의 비동기 소스에 선언적 종속성을 가진다. 쿼리는 Promise기반 메서드(GET 및 POST)와 함께 사용하여 서버에서 데이터를 가져올 수 있다. 메서드가 수정하는 경우에는 Mutation을 사용한다
- 컴포넌트 또는 커스텀 훅에 쿼리를 구독하고 사용하고 싶다면
useQuery
훅을 호출한다- 데이터 처리 성공하거나 에러가 반환된다
import { useQuery } from 'react-query'
function App() {
const info = useQuery('todos', fetchTodoList)
}
unique key는 애플리케이션 전체에서 쿼리를 다시 가져오고, 캐싱하고, 공유하기 위해 내부에서 사용된다
useQuery
에서 반환된 쿼리 결과에는 템플릿 작성 및 기타 쿼리에 필요한 모든 정보가 포함되어있다.
result object에는 주어진 순간에 다음 상태 중 하나만 있을 수 있다. (주요상태)
그 이외에도 상태에 의존하여 사용가능한 정보들이 있다
Mutations
는 일반적으로 데이터를 create,update,delete시 사용useMutation
훅을 보낸다 function App() {
const mutation = useMutation(newTodo => {
return axios.post('/todos', newTodo)
})
return (
<div>
{mutation.isLoading ? (
'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>
)
}
onSuccess
, invalidateQueries
method, setQueryData
method를 같이 사용하면 mutation은 강력한 도구가 된다Resetting Mutation State
{mutation.error && (
<h5 onClick={() => mutation.reset()}>{mutation.error}</h5>
)}
Mutation Side Effects
useMutation
은 mutation 라이프사이클동안 빠르고 쉽게 사이드 이펙트를 적용하도록 도와주는 옵션들이 있다. 이것들은 mutation 그리고 업데이트 이후 쿼리를 무효화하고 다시 가져오는데 유용하다 useMutation(addTodo, {
onMutate: variables => {
// A mutation is about to happen!
// Optionally return a context containing data to use when for example rolling back
return { id: 1 }
},
onError: (error, variables, context) => {
// An error happened!
console.log(`rolling back optimistic update with id ${context.id}`)
},
onSuccess: (data, variables, context) => {
// Boom baby!
},
onSettled: (data, error, variables, context) => {
// Error or success... doesn't matter!
},
})
Consecutive mutations
useMutation(addTodo, {
onSuccess: (data, error, variables, context) => {
// Will be called 3 times
},
})
['Todo 1', 'Todo 2', 'Todo 3'].forEach((todo) => {
mutate(todo, {
onSuccess: (data, error, variables, context) => {
// Will execute only once, for the last mutation (Todo 3),
// regardless which mutation resolves first
},
})
})
Promises
const mutation = useMutation(addTodo)
try {
const todo = await mutation.mutateAsync(todo)
console.log(todo)
} catch (error) {
console.error(error)
} finally {
console.log('done')
}
Retry
Retry
const mutation = useMutation(addTodo, {
retry: 3,
})
Persist mutations
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('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('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:
//일시중지된 mutation을 재개
queryClient.resumePausedMutations()
invalidateQueries
메서드를 통해 QueryClient에 오래된 쿼리를 표시하고 잠재적으로 가져 올 수 있다 // Invalidate every query in the cache
queryClient.invalidateQueries()
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries('todos')
쿼리가 invalidateQueries로 무효화되면 두가지 일이 발생한다
Query Matching with invalidateQueries
invalidateQueries 및 removeQueries와 같은 API를 사용하는 경우 여러 쿼리를 일치시키거나 실제로 특정정확한 쿼리를 일치시킬 수 있다.
import { useQuery, useQueryClient } from 'react-query'
// Get QueryClient from the context
const queryClient = useQueryClient()
queryClient.invalidateQueries('todos')
// Both queries below will be invalidated
const todoListQuery = useQuery('todos', fetchTodoList)
const todoListQuery = useQuery(['todos', { page: 1 }], fetchTodoList)
- 특정 키를 invalidateQueries메서드에 전달하여 특정 변수가 있는 쿼리를 무효화할 수 있다
```jsx
queryClient.invalidateQueries(['todos', { type: 'done' }])
// The query below will be invalidated
const todoListQuery = useQuery(['todos', { type: 'done' }], fetchTodoList)
// However, the following query below will NOT be invalidated
const todoListQuery = useQuery('todos', fetchTodoList)
queryClient.invalidateQueries('todos', { exact: true })
// The query below will be invalidated
const todoListQuery = useQuery(['todos'], fetchTodoList)
// However, the following query below will NOT be invalidated
const todoListQuery = useQuery(['todos', { type: 'done' }], fetchTodoList)
queryClient.invalidateQueries({
predicate: query =>
query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10,
})
// The query below will be invalidated
const todoListQuery = useQuery(['todos', { version: 20 }], fetchTodoList)
// The query below will be invalidated
const todoListQuery = useQuery(['todos', { version: 10 }], fetchTodoList)
// However, the following query below will NOT be invalidated
const todoListQuery = useQuery(['todos', { version: 5 }], fetchTodoList)