DEV Community

A0mineTV
A0mineTV

Posted on

πŸš€ Creating a Feature-Rich Redux Store with Redux Toolkit and TypeScript

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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[];
}
Enter fullscreen mode Exit fullscreen mode

Β 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;
Enter fullscreen mode Exit fullscreen mode

Key Points:

  • The component uses useAppSelector to access the state and useAppDispatch 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)