TypeScript Redux lost state upon refreshing page

83 Views Asked by At

redux/reducer/cartReducer.ts

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { CartReducerInitialState } from "../../types/reducer-types";
import { CartItem, ShippingInfo } from "../../types/types";

const initialState: CartReducerInitialState = {
    loading: false,
    cartItems: [],
    subtotal: 0,
    tax: 0,
    shippingCharges: 0,
    discount: 0,
    total: 0,
    shippingInfo: {
        address: "",
        city: "",
        state: "",
        country: "",
        pinCode: "",
    },
};

export const cartReducer = createSlice({
  name: "cartReducer",
  initialState,
  reducers: {
    addToCart: (state, action: PayloadAction<CartItem>) => {
      state.loading = true;

      const index = state.cartItems.findIndex(
        (i) => i.productId === action.payload.productId
      );

      if (index !== -1) state.cartItems[index] = action.payload;
      else state.cartItems.push(action.payload);
      state.loading = false;
    },

    removeCartItem: (state, action: PayloadAction<string>) => {
      state.loading = true;
      state.cartItems = state.cartItems.filter(
        (i) => i.productId !== action.payload
      );
      state.loading = false;
    }
  },
});

export const {
  addToCart,
  removeCartItem
} = cartReducer.actions;

redux/store.ts

import { configureStore } from "@reduxjs/toolkit";
import { userAPI } from "./api/userAPI";
import { userReducer } from "./reducer/userReducer";
import { productAPI } from "./api/productAPI";
import { cartReducer } from "./reducer/cartReducer";
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import {combineReducers} from "@reduxjs/toolkit"; 

export const server = import.meta.env.VITE_SERVER;

const reducers = combineReducers({
    [userAPI.reducerPath]: userAPI.reducer,
    [productAPI.reducerPath]: productAPI.reducer,
    [userReducer.name]: userReducer.reducer,
    [cartReducer.name]: cartReducer.reducer           
});

const persistConfig = {
    key: 'root',
    storage,
    whitelist: ["cartReducer"]
};

const persistedReducer = persistReducer(persistConfig, reducers)

export const store = configureStore({
    reducer: persistedReducer,
    middleware: (getDefaultMiddleware) => 
        getDefaultMiddleware().concat([
            userAPI.middleware,
            productAPI.middleware
        ]),
});

export type RootState = ReturnType<typeof store.getState>

pages/Home.tsx

 const { cartItems, subtotal, tax, total, shippingCharges, discount } = useSelector((state: { cartReducer: CartReducerInitialState }) => state.cartReducer)
    
    const { data, isError, isLoading } = useLatestProductsQuery("")
    // console.log(data)
    console.log(cartItems)

    const dispatch = useDispatch()

    const addToCartHandler = (cartItem: CartItem) => {
        if(cartItem.stock < 1) return toast.error("Out of Stock")
        dispatch(addToCart(cartItem))
        toast.success("Item added to cart")
    }
     {
                            data?.products.map((i) => (
                                <ProductCard key={i._id} productId={i._id} name={i.name} price={i.price}
                                category={i.category} stock={i.stock} handler={addToCartHandler} photo={i.photo} />
                            ))
                        }

components/ProductCard.tsx

type ProductProps = {
    productId: string
    photo: string
    name: string
    category: string 
    price: number
    stock: number
    handler: (cartItem: CartItem) => string | undefined
}

const ProductCard = ({
    productId,
    photo,
    name,
    category,
    price,
    stock,
    handler
}: ProductProps) => {
    return (
        <div className="product-card">
            <h3 className="category">{category.toUpperCase()}</h3>
            <img src={`${server}/${photo}`} alt={name} />
            <p>{name}</p>
            <span>HK$ {price}</span>
            <div>
                <button onClick={() => handler({
                    productId,
                    price,
                    name,
                    category,
                    photo,
                    stock,
                    quantity: 1
                })}>
                    <FaPlus />
                </button>

            </div>
        </div>
    )
}

types/types.ts

export type CartItem = {
    productId: string
    photo: string
    name: string
    category: string
    price: number
    quantity: number
    stock: number
}

In this project, I would like to maintain the state of the cart that has been added when the user clicked on the add button. Even I registered the store correctly with the reducer, the cartReducer could not maintain the items added after clicking the button. Till now I tried to add redux-persist but the state still could not be maintained when I click the add button and refresh. I had tried numerous times by following the documentation in redux but still stuck here so can anyone provide help to solve the issue?

1

There are 1 best solutions below

4
Drew Reese On BEST ANSWER

You appear to have implemented about half of the persistence setup. What is missing is creating a persistor object and possibly the PersistGate.

import { configureStore, combineReducers } from "@reduxjs/toolkit";
import {
  persistStore, // <-- add import
  persistReducer,
  FLUSH, // <-- import actions to ignore in serializability check
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from 'redux-persist';
import storage from 'redux-persist/lib/storage';

import { userAPI } from "./api/userAPI";
import { userReducer } from "./reducer/userReducer";
import { productAPI } from "./api/productAPI";
import { cartReducer } from "./reducer/cartReducer";

export const server = import.meta.env.VITE_SERVER;

const reducers = combineReducers({
  [userAPI.reducerPath]: userAPI.reducer,
  [productAPI.reducerPath]: productAPI.reducer,
  [userReducer.name]: userReducer.reducer,
  [cartReducer.name]: cartReducer.reducer           
});

const persistConfig = {
  key: 'root',
  storage,
  whitelist: [cartReducer.name]
};

const persistedReducer = persistReducer(persistConfig, reducers)

export const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) => 
    getDefaultMiddleware({
      serializableCheck: { // <-- configure serializability middleware
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat([
      userAPI.middleware,
      productAPI.middleware
    ]),
});

export const persistor = persistStore(store); // <-- export persistor

export type RootState = ReturnType<typeof store.getState>

Then in the UI ensure you are wrapping the app with the PersistGate under the Redux Provider component.

Example:

import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './path/to/redux/store';

const AppRoot = () => {
  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  );
};