In order to run this project you need to start both the client and the server(API).
NOTE: For better undertanding of the Performance and Caching behaviour you can throttle the network speed to Slow 3G, and use the integrated react-query-devtools
to understand the query/mutation flow.
cd client
npm run dev
cd server
npm start
- Data fetching is fairly simple, managing the asynchronous state is difficult!
- React Query is a powerful asynchronous state management tool/library!
- React query is a library that simplifies the way we fetch, cache and synchronize data.
- React itself has no opinion about how we fetch data - We can use native browser
fetch
API oraxios
or anything. In order to do this type of normal data fetching we also need the hooks likeuseState
andUseEffect
. - It is difficult to manage when we have the requirements like caching, retries, etc. React Query handles all these caching, retries, invalidation and much more out of the box.
- There are many other features like Auto Refetching, Parallel Queries, Dependent Queries, Infinite scroll, Offline Support, Window Focus Refetching, Scroll Recovery, Offline Support, Request Cancellation, Mutations API etc
- It is one of the most important hook in React Query to fetch and manage some data from a server.
- We can choose any fetching logic (fetch, axios) it needs to have a promise.
useQuery
must needs aqueryKey
and aqueryFn
.
Example:
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import axios from 'axios'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
function Example() {
const { isPending, error, data, isFetching } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
axios
.get('https://api.github.com/repos/tannerlinsley/react-query')
.then((res) => res.data),
})
if (isPending) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
<div>{isFetching ? 'Updating...' : ''}</div>
<ReactQueryDevtools initialIsOpen />
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.createRoot(rootElement).render(<App />)
There are many other optional params that we can add to the useQuery
such as staleTime
, gcTime
, refetchOnWindowFocus
, etc
- Providing a stale time not actually refetch a query. Stale queries are refethed automatically in the background when few things are happened:
- When the new instance of the query is mounted
- When the window is re-focused
- When the network is re-connected
- On the provided
refetchInterval
- We can provide a default
staleTime
that applies to all the queries/mutations in the mainqueryClient
declaration
const queryClient = new QueryClient({defaultOptions: {queries: {stateTime: 6000}}})
- By default 5 minutes
const queryClient = new QueryClient({defaultOptions: {queries: {stateTime: 6000, gcTime: 10*(60*1000)}}})
true
by default
- By default 3
- We can provide a
retryDelay
also
- To update the state of a server
- There should be a
mutationFn
- There different mutation life cycles for
useMutation
:onMutate
,onError
,onSuccess
,onSettled
Example:
const createPostMutation = useMutation({
mutationFn: createPost,
// mutationFn: (variables) => createPost(variables),
// The above mentioned variables is the exact same values we are passing to the mutate function of useMutation
// It is the default first paramter, so we dont need to pass it explicitly!
onSuccess: data => {
queryClient.setQueryData(["posts", data.id], data)
queryClient.invalidateQueries({ queryKey: ['posts']})
},
onMutate: (variables) => {}, // onMutate will be called before the mutationFn
onError: (error, variables, context) => {},
onSettled: (data, error, variables, context) => {},
})
const BASE_URL = "http://localhost:3000/";
const axiosInstance = axios.create({baseURL: BASE_URL});
export const createPost = ({ title, body }: {title: string, body: string }) => {
return axiosInstance
.post("posts", {
title,
body,
userId: Math.floor(Math.random() * 5) + 1, // To assign a random user from the list of 5
id: String(Date.now()),
})
.then(res => res.data)
}
Now we can use the actual mutate function from the createPostMutation to update the data
createPostMutation.mutate({
title: titleRef.current.value,
body: bodyRef.current.value,
});
We can also access the other states such as error and pending.
- One query depends the other
- Based on the
enabled
parameter
Read the official docs here for more details