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?
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>
);
};