RTK Query Basics
RTK Query is a data fetching tool built on top of Redux Toolkit. It makes API calls super easy and handles loading states, caching, and more automatically.
What RTK Query Does
- Fetches data from APIs
- Caches responses so you don't re-fetch the same data
- Handles loading states (loading, success, error)
- Auto-refetches when data gets stale
- Optimistic updates for better UX
Basic Setup
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
// Create API slice
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api',
}),
tagTypes: ['Post', 'User'],
endpoints: (builder) => ({
// Get all posts
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Post'],
}),
// Get single post
getPost: builder.query({
query: (id) => `/posts/${id}`,
providesTags: (result, error, id) => [{ type: 'Post', id }],
}),
// Create new post
addPost: builder.mutation({
query: (newPost) => ({
url: '/posts',
method: 'POST',
body: newPost,
}),
invalidatesTags: ['Post'],
}),
}),
})
// Export hooks
export const {
useGetPostsQuery,
useGetPostQuery,
useAddPostMutation,
} = apiSlice
Add to Store
import { configureStore } from '@reduxjs/toolkit'
import { apiSlice } from './api/apiSlice'
export const store = configureStore({
reducer: {
api: apiSlice.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware),
})
Using in Components
Fetching Data
import { useGetPostsQuery } from './api/apiSlice'
function PostsList() {
const {
data: posts,
isLoading,
isSuccess,
isError,
error
} = useGetPostsQuery()
if (isLoading) return <div>Loading...</div>
if (isError) return <div>Error: {error.message}</div>
return (
<div>
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
))}
</div>
)
}
Creating Data
import { useAddPostMutation } from './api/apiSlice'
function AddPost() {
const [addPost, { isLoading }] = useAddPostMutation()
const handleSubmit = async (e) => {
e.preventDefault()
try {
await addPost({
title: 'New Post',
content: 'This is my post content'
}).unwrap()
alert('Post created!')
} catch (error) {
alert('Failed to create post')
}
}
return (
<form onSubmit={handleSubmit}>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Creating...' : 'Create Post'}
</button>
</form>
)
}
Query vs Mutation
Queries (GET data)
- Use
builder.query() - For fetching/reading data
- Auto-run when component mounts
- Return
data,isLoading,error, etc.
Mutations (POST/PUT/DELETE)
- Use
builder.mutation() - For creating/updating/deleting data
- Must be triggered manually
- Return
[trigger, result]array
Tags and Caching
Tags help control when data gets refetched:
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
// This query provides 'Post' data
providesTags: ['Post'],
}),
addPost: builder.mutation({
query: (newPost) => ({
url: '/posts',
method: 'POST',
body: newPost,
}),
// When this runs, refetch anything tagged 'Post'
invalidatesTags: ['Post'],
}),
})
Authentication
const apiSlice = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token
if (token) {
headers.set('authorization', `Bearer ${token}`)
}
return headers
},
}),
// ... rest of config
})
Error Handling
const apiSlice = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/api',
}),
endpoints: (builder) => ({
getUser: builder.query({
query: (id) => `/users/${id}`,
transformErrorResponse: (response, meta, arg) => {
return {
status: response.status,
message: response.data?.message || 'Something went wrong'
}
},
}),
}),
})
Transform Response
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
transformResponse: (response) => {
// Transform API response before storing in cache
return response.data.map(post => ({
id: post.id,
title: post.title,
author: post.user.name, // Flatten nested data
createdAt: new Date(post.created_at), // Convert dates
}))
},
}),
})
Polling (Auto-refresh)
function LivePosts() {
const { data } = useGetPostsQuery(undefined, {
pollingInterval: 5000, // Refetch every 5 seconds
})
return <div>{/* render posts */}</div>
}
Skip Queries
function UserProfile({ userId }) {
const { data: user } = useGetUserQuery(userId, {
skip: !userId, // Don't run query if no userId
})
return user ? <div>{user.name}</div> : null
}
Key Benefits
- Less code - No need to write loading/error logic
- Automatic caching - Same requests are cached
- Background refetching - Keeps data fresh
- TypeScript support - Great type safety
- DevTools integration - See API calls in Redux DevTools
When to Use RTK Query
- Making API calls
- Need caching and background sync
- Want automatic loading states
- Working with REST APIs
- Building data-heavy apps
RTK Query replaces libraries like React Query or SWR when you're already using Redux!