Redux Toolkit simplifies state management in modern React applications by providing a standardized way to write Redux logic. In this article, we'll walk through the creation of a ProductSlice to manage product data, categories, and wishlist functionality using Redux Toolkit with TypeScript.
1. Setting Up the Redux Store
First, configure your Redux store using configureStore
from Redux Toolkit. This provides an enhanced development experience with built-in middlewares and devtools.
Code: store.ts
import { configureStore } from "@reduxjs/toolkit";
import productReducer from "./features/ProductSlice";
export const store = configureStore({
reducer: {
product: productReducer, // Adding the product slice reducer
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Here:
RootState
infers the global state shape.AppDispatch
simplifies typing when dispatching actions in components.
2. Adding Typed Hooks for Redux
To avoid repetitive typing, we'll create custom hooks that enforce the types of our state and dispatch.
Code: hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { RootState, AppDispatch } from "./store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Use these hooks instead of the default useSelector
and useDispatch
to ensure proper type inference.
3. Defining the Product Slice
The ProductSlice
manages all product-related data:
Products
Categories
Wishlist
We'll define the initial state, actions, and reducers using Redux Toolkit's createSlice
.
Code: ProductSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { ProductSlice } from "../../models/ProductSlice";
import { Product } from "../../models/Product";
import { Category } from "../../models/Category";
// Initial state
const initialState: ProductSlice = {
allProducts: [],
categories: [],
newProducts: [],
featuredProducts: [],
wishlist: [], // Ensure consistent naming convention
};
export const productSlice = createSlice({
name: "productSlice",
initialState,
reducers: {
updateNewList: (state, action: PayloadAction<Product[]>) => {
state.newProducts = action.payload;
},
updateFeaturedList: (state, action: PayloadAction<Product[]>) => {
state.featuredProducts = action.payload;
},
addToWishlist: (state, action: PayloadAction<Product>) => {
if (!state.wishlist.some((item) => item.id === action.payload.id)) {
state.wishlist.push(action.payload);
}
},
addCategories: (state, action: PayloadAction<Category[]>) => {
state.categories = action.payload;
},
addProducts: (state, action: PayloadAction<Product[]>) => {
state.allProducts = action.payload;
},
},
});
// Exporting actions
export const {
updateNewList,
updateFeaturedList,
addToWishlist,
addCategories,
addProducts,
} = productSlice.actions;
export default productSlice.reducer;
Highlights:
1. State Management
- Tracks multiple lists (
allProducts
,newProducts
,wishlist
).
2. Actions
- Each action updates specific parts of the state, maintaining immutability.
- Example:
addToWishlist
checks for duplicates before adding a new product.
3. Immer Integration
- Redux Toolkit uses Immer under the hood, allowing mutable-style updates that are safe and performant.
4. Models for Type Safety
We use TypeScript interfaces to ensure the data structure is consistent.
Code: Models
// Product.ts
export interface Product {
id: number;
name: string;
price: number;
category: string;
// Add other fields as needed
}
// Category.ts
export interface Category {
id: number;
name: string;
}
// ProductSlice.ts
export interface ProductSlice {
allProducts: Product[];
categories: Category[];
newProducts: Product[];
featuredProducts: Product[];
wishlist: Product[];
}
Β 5. Using the Product Slice in React
To demonstrate, here's an example of a component that displays and manages the wishlist.
Code: WishlistComponent.tsx
import React from "react";
import { useAppSelector, useAppDispatch } from "../store/hooks";
import { addToWishlist } from "../features/ProductSlice";
const WishlistComponent: React.FC = () => {
const wishlist = useAppSelector((state) => state.product.wishlist);
const dispatch = useAppDispatch();
const addProductToWishlist = () => {
const product = { id: 1, name: "Sample Product", price: 100, category: "Sample" };
dispatch(addToWishlist(product));
};
return (
<div>
<h2>Wishlist</h2>
<button onClick={addProductToWishlist}>Add to Wishlist</button>
<ul>
{wishlist.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
};
export default WishlistComponent;
Key Points:
The component uses
useAppSelector
to access the state anduseAppDispatch
to dispatch actions.The
addToWishlist
reducer ensures no duplicate products are added.
Conclusion
Redux Toolkit makes managing complex states straightforward, even in large applications. Combined with TypeScript, you gain the benefits of a strongly-typed, developer-friendly ecosystem. By following the structure outlined in this article, you can build scalable state management solutions with ease.
What are your thoughts on using Redux Toolkit with TypeScript? Share your experiences in the comments! π
Top comments (0)