React Query
Powerful data fetching and caching for Next.js
🔄 What is React Query?
React Query simplifies data fetching, caching, and synchronization in Next.js applications. It automatically manages server state, handles loading and error states, and provides powerful caching mechanisms for optimal performance.
// Simple React Query example
import { useQuery } from '@tanstack/react-query'
function Users() {
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json())
})
if (isLoading) return 'Loading...'
return <div>{data.map(user => user.name)}</div>
}
Key React Query Features
Data Fetching
Fetch data with automatic caching
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
Caching
Smart caching reduces API calls
useQuery({
queryKey: ['user', id],
staleTime: 5000
})
Mutations
Update data with useMutation
useMutation({
mutationFn: createPost,
onSuccess: () => {}
})
Auto Refetch
Keep data fresh automatically
useQuery({
refetchOnWindowFocus: true,
refetchInterval: 30000
})
🔹 Setup React Query in Next.js
React Query requires a QueryClient provider to manage cache and queries. Set it up in your root layout for App Router or _app.js for Pages Router.
// app/providers.jsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export default function Providers({ children }) {
const [queryClient] = useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
// app/layout.jsx
import Providers from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
🔹 Basic useQuery Example
useQuery fetches data and provides loading, error, and success states automatically. The queryKey uniquely identifies the query for caching purposes.
// app/posts/page.jsx
'use client'
import { useQuery } from '@tanstack/react-query'
export default function PostsPage() {
const { data, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
return res.json()
}
})
if (isLoading) return <div>Loading posts...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
<h1>Posts</h1>
{data.slice(0, 5).map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
</div>
)
}
Output:
Posts
Sample Post Title
This is a sample post body text that would be fetched from the API...
🔹 useMutation for Updates
useMutation handles POST, PUT, DELETE operations. It provides callbacks for success and error handling, making it easy to update UI after data changes.
// components/CreatePost.jsx
'use client'
import { useMutation, useQueryClient } from '@tanstack/react-query'
export default function CreatePost() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: async (newPost) => {
const res = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(newPost)
})
return res.json()
},
onSuccess: () => {
// Invalidate and refetch posts
queryClient.invalidateQueries({ queryKey: ['posts'] })
}
})
const handleSubmit = (e) => {
e.preventDefault()
mutation.mutate({ title: 'New Post', body: 'Content' })
}
return (
<form onSubmit={handleSubmit}>
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Creating...' : 'Create Post'}
</button>
{mutation.isError && <p>Error: {mutation.error.message}</p>}
</form>
)
}
🔹 Query with Parameters
Pass dynamic parameters to queries using the queryKey array. React Query automatically refetches when parameters change, keeping your data synchronized.
// app/user/[id]/page.jsx
'use client'
import { useQuery } from '@tanstack/react-query'
export default function UserProfile({ params }) {
const { data: user } = useQuery({
queryKey: ['user', params.id],
queryFn: async () => {
const res = await fetch(`/api/users/${params.id}`)
return res.json()
}
})
return (
<div>
<h1>{user?.name}</h1>
<p>{user?.email}</p>
</div>
)
}
🔹 Prefetching Data
Prefetch data before users navigate to improve perceived performance. This loads data in the background so it's instantly available when needed.
// components/PostLink.jsx
'use client'
import { useQueryClient } from '@tanstack/react-query'
import Link from 'next/link'
export default function PostLink({ postId }) {
const queryClient = useQueryClient()
const prefetchPost = () => {
queryClient.prefetchQuery({
queryKey: ['post', postId],
queryFn: () => fetch(`/api/posts/${postId}`).then(r => r.json())
})
}
return (
<Link
href={`/posts/${postId}`}
onMouseEnter={prefetchPost}
>
View Post
</Link>
)
}