Redux Toolkit RTK Query: Complete Guide
RTK Query is a powerful data fetching and caching library built on top of Redux Toolkit. It simplifies API data management in React applications by providing automatic caching, request deduplication, and optimistic updates. In this guide, we'll learn how to use RTK Query in an inventory management system.
RTK Query is a powerful data fetching and caching library built on top of Redux Toolkit. It simplifies API data management in React applications by providing automatic caching, request deduplication, and optimistic updates. In this guide, we'll learn how to use RTK Query in an inventory management system.
Installation
npm install @reduxjs/toolkit react-reduxSetting Up RTK Query API Slice
Creating a products API slice:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const productsApiSlice = createApi({
reducerPath: "products",
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:3000/api",
prepareHeaders: (headers, { getState }) => {
const token = (getState() as any).auth?.token;
if (token) {
headers.set("authorization", `Bearer ${token}`);
}
return headers;
},
}),
tagTypes: ["Product"],
endpoints: (builder) => ({
getProducts: builder.query({
query: () => "/products",
providesTags: (result) =>
result?.data
? [
...result.data.map(({ id }: { id: string }) => ({
type: "Product" as const,
id
})),
{ type: "Product", id: "LIST" },
]
: [{ type: "Product", id: "LIST" }],
}),
getProductById: builder.query({
query: (id: string) => "/products/" + id,
providesTags: (result, error, id) => [{ type: "Product", id }],
}),
addProduct: builder.mutation({
query: (productData) => ({
url: "/products",
method: "POST",
body: productData,
formData: true,
}),
invalidatesTags: [{ type: "Product", id: "LIST" }],
}),
updateProduct: builder.mutation({
query: ({ id, formData }) => ({
url: "/products/" + id,
method: "PUT",
body: formData,
formData: true,
}),
invalidatesTags: (result, error, arg) => [
{ type: "Product", id: arg.id },
{ type: "Product", id: "LIST" },
],
}),
deleteProduct: builder.mutation({
query: (id) => ({
url: "/products/" + id,
method: "DELETE",
}),
invalidatesTags: (result, error, id) => [
{ type: "Product", id },
{ type: "Product", id: "LIST" },
],
}),
}),
});
export const {
useGetProductsQuery,
useGetProductByIdQuery,
useAddProductMutation,
useUpdateProductMutation,
useDeleteProductMutation,
} = productsApiSlice;Configuring Redux Store
import { configureStore } from "@reduxjs/toolkit";
import { productsApiSlice } from "./products/productSlice";
import { categoriesApiSlice } from "./categories/categorySlice";
export const store = configureStore({
reducer: {
[productsApiSlice.reducerPath]: productsApiSlice.reducer,
[categoriesApiSlice.reducerPath]: categoriesApiSlice.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(
productsApiSlice.middleware,
categoriesApiSlice.middleware
),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;Using RTK Query Hooks
Using the generated hooks in components:
import { useGetProductsQuery, useDeleteProductMutation } from "../../state/products/productSlice";
function Products() {
const { data, isLoading, isError, error } = useGetProductsQuery({});
const [deleteProduct, { isLoading: isDeleting }] = useDeleteProductMutation();
const handleDelete = async (id: string) => {
try {
await deleteProduct({ id }).unwrap();
toast.success("Product deleted successfully");
} catch (error) {
toast.error("Failed to delete product");
}
};
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error loading products</div>;
const products = data?.data || [];
return (
<div>
{products.map((product) => (
<div key={product.id}>
<h3>{product.name}</h3>
<button onClick={() => handleDelete(product.id)} disabled={isDeleting}>
Delete
</button>
</div>
))}
</div>
);
}Advanced Features
Polling
const { data } = useGetProductsQuery(
{},
{
pollingInterval: 5000, // Poll every 5 seconds
}
);Conditional Queries
const { data } = useGetProductByIdQuery(productId, {
skip: !productId, // Skip query if productId is falsy
});Best Practices
- Use tag-based cache invalidation for automatic refetching
- Implement optimistic updates for better UX
- Use skip option for conditional queries
- Configure baseQuery with authentication headers
- Organize API slices by feature domain
- Use unwrap() for mutation error handling
- Implement proper error handling in components
Conclusion
RTK Query provides a powerful, type-safe solution for API data management in React applications. With automatic caching, request deduplication, and optimistic updates, it simplifies complex data fetching scenarios. This makes it perfect for inventory management systems and other data-heavy applications.