How to use Redux Toolkit in React
Redux Toolkit’s RTK Query is the most powerful data fetching and caching solution for Redux applications, automating loading states, error handling, and cache invalidation that you’d otherwise write by hand.
As the creator of CoreUI with 25 years of front-end development experience, I use RTK Query in complex React dashboards where multiple components fetch the same data and need to stay synchronized without redundant API calls.
Define your API once with createApi, and RTK Query generates typed hooks, manages caching, handles request deduplication, and automatically re-fetches stale data.
This eliminates the need to write fetchUsers.pending, fetchUsers.fulfilled, and fetchUsers.rejected reducers for every endpoint.
Define your API with createApi.
// src/store/apiSlice.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers) => {
const token = localStorage.getItem('token')
if (token) headers.set('Authorization', `Bearer ${token}`)
return headers
}
}),
tagTypes: ['User', 'Post'],
endpoints: (builder) => ({
// Query endpoint
getUsers: builder.query({
query: (params = {}) => ({
url: '/users',
params
}),
providesTags: ['User']
}),
getUserById: builder.query({
query: (id) => `/users/${id}`,
providesTags: (result, error, id) => [{ type: 'User', id }]
}),
// Mutation endpoint
createUser: builder.mutation({
query: (body) => ({
url: '/users',
method: 'POST',
body
}),
invalidatesTags: ['User'] // Refetch user list after creating
}),
updateUser: builder.mutation({
query: ({ id, ...body }) => ({
url: `/users/${id}`,
method: 'PUT',
body
}),
invalidatesTags: (result, error, { id }) => [{ type: 'User', id }]
}),
deleteUser: builder.mutation({
query: (id) => ({ url: `/users/${id}`, method: 'DELETE' }),
invalidatesTags: ['User']
})
})
})
export const {
useGetUsersQuery,
useGetUserByIdQuery,
useCreateUserMutation,
useUpdateUserMutation,
useDeleteUserMutation
} = api
tagTypes and providesTags/invalidatesTags form the cache invalidation system. When createUser succeeds, it invalidates the User tag, causing getUsers to automatically re-fetch.
Configure the Store
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit'
import { api } from './apiSlice'
export const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer
},
middleware: (getDefault) => getDefault().concat(api.middleware)
})
Using RTK Query Hooks in Components
// UserList.jsx
import { useGetUsersQuery, useDeleteUserMutation } from './store/apiSlice'
export function UserList() {
const { data: users, isLoading, error } = useGetUsersQuery()
const [deleteUser, { isLoading: isDeleting }] = useDeleteUserMutation()
if (isLoading) return <p>Loading...</p>
if (error) return <p>Error: {error.message}</p>
return (
<ul>
{users?.map(user => (
<li key={user.id}>
{user.name}
<button
onClick={() => deleteUser(user.id)}
disabled={isDeleting}
>
Delete
</button>
</li>
))}
</ul>
)
}
useGetUsersQuery() returns { data, isLoading, isFetching, error, refetch }. The hook deduplicates requests — if multiple components call useGetUsersQuery() simultaneously, only one network request is made. After deleteUser succeeds, the User tag is invalidated and the list automatically re-fetches.
Best Practice Note
This is the recommended data fetching pattern for Redux-based CoreUI React applications. RTK Query eliminates the need for separate loading/error state in your slices for server data. Keep server state in RTK Query and client-only state in regular slices. For non-Redux applications, React Query (TanStack Query) provides the same caching pattern without requiring Redux. See how to use Redux in React for the foundational Redux Toolkit setup.



