To install TanStack Query in your project:
npm install @tanstack/react-query
# or
yarn add @tanstack/react-query
First, wrap your application with QueryClientProvider:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient()
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* Your app components */}
</QueryClientProvider>
)
}
Queries are used to fetch data from a server. Here's a basic example:
import { useQuery } from '@tanstack/react-query'
function GetTodos() {
const { isLoading, error, data } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('https://api.example.com/todos').then(res => res.json()),
})
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
Mutations are used to create/update/delete data:
import { useMutation } from '@tanstack/react-query'
function AddTodo() {
const mutation = useMutation({
mutationFn: newTodo => {
return fetch('https://api.example.com/todos', {
method: 'POST',
body: JSON.stringify(newTodo),
})
},
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
return (
<form onSubmit={(e) => {
e.preventDefault()
mutation.mutate({ title: 'New Todo' })
}}>
<button type="submit">Add Todo</button>
</form>
)
}
Invalidate queries to refetch fresh data:
queryClient.invalidateQueries({ queryKey: ['todos'] })
Prefetch data before it's needed:
const prefetchTodos = async () => {
await queryClient.prefetchQuery({
queryKey: ['todos'],
queryFn: () => fetch('https://api.example.com/todos').then(res => res.json()),
})
}
For pagination or infinite scrolling:
import { useInfiniteQuery } from '@tanstack/react-query'
function InfiniteTodos() {
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
status,
} = useInfiniteQuery({
queryKey: ['todos'],
queryFn: ({ pageParam = 0 }) =>
fetch(`https://api.example.com/todos?page=${pageParam}`).then(res => res.json()),
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
return status === 'loading' ? (
<p>Loading...</p>
) : status === 'error' ? (
<p>Error: {error.message}</p>
) : (
<>
{data.pages.map((group, i) => (
<React.Fragment key={i}>
{group.map(todo => (
<p key={todo.id}>{todo.title}</p>
))}
</React.Fragment>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
<div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
</>
)
}
When a query depends on the result of another:
function UserProjects() {
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
const { data: projects } = useQuery({
queryKey: ['projects', user?.id],
queryFn: () => fetchProjects(user.id),
enabled: !!user,
})
// ...
}
Configure caching behavior:
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
staleTime: 60 * 1000, // 1 minute
cacheTime: 5 * 60 * 1000, // 5 minutes
})
Handle errors in queries:
const { isError, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
retry: 3, // Retry 3 times before failing
onError: (error) => {
console.error('An error occurred:', error)
},
})
Update UI optimistically before server confirmation:
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos', newTodo.id] })
const previousTodo = queryClient.getQueryData(['todos', newTodo.id])
queryClient.setQueryData(['todos', newTodo.id], newTodo)
return { previousTodo }
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos', newTodo.id], context.previousTodo)
},
onSettled: (newTodo) => {
queryClient.invalidateQueries({ queryKey: ['todos', newTodo.id] })
},
})
Implement pagination:
function PaginatedTodos() {
const [page, setPage] = useState(1)
const { data, isLoading, isPreviousData } = useQuery({
queryKey: ['todos', page],
queryFn: () => fetchTodos(page),
keepPreviousData: true,
})
return (
<div>
{isLoading ? (
<div>Loading...</div>
) : (
<ul>
{data.todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)}
<button
onClick={() => setPage(old => Math.max(old - 1, 1))}
disabled={page === 1}
>
Previous Page
</button>
<button
onClick={() => setPage(old => (!isPreviousData && data.hasMore ? old + 1 : old))}
disabled={isPreviousData || !data?.hasMore}
>
Next Page
</button>
</div>
)
}
Configure for SSR:
import { QueryClient, dehydrate } from '@tanstack/react-query'
export async function getServerSideProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
function MyApp({ Component, pageProps }) {
const [queryClient] = useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient}>
<Hydrate state={pageProps.dehydratedState}>
<Component {...pageProps} />
</Hydrate>
</QueryClientProvider>
)
}
Test components using React Query:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen } from '@testing-library/react'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})
const wrapper = ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
test('TodoList shows todos', async () => {
render(<TodoList />, { wrapper })
expect(await screen.findByText('Todo 1')).toBeInTheDocument()
})
Example of using select:
const { data: todoCount } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
select: (data) => data.length,
})
Example of using placeholderData:
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
placeholderData: previousTodos,
})
2024 © All rights reserved - buraxta.com