https://github.com/dev-emekaa/redux-e-com-guide
An E-commerce product page Template with React, Sass and Redux ToolKit. Easy to set up and understand. Can use as a starter file to configure your own product page with redux.
https://github.com/dev-emekaa/redux-e-com-guide
react redux redux-toolkit sass
Last synced: about 1 month ago
JSON representation
An E-commerce product page Template with React, Sass and Redux ToolKit. Easy to set up and understand. Can use as a starter file to configure your own product page with redux.
- Host: GitHub
- URL: https://github.com/dev-emekaa/redux-e-com-guide
- Owner: dev-emekaa
- Created: 2024-02-08T14:43:47.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-02-08T15:05:39.000Z (over 2 years ago)
- Last Synced: 2025-03-30T01:38:14.575Z (about 1 year ago)
- Topics: react, redux, redux-toolkit, sass
- Language: SCSS
- Homepage:
- Size: 53.7 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# E-commerce Product Page Template with Redux Store and Redux ToolKit
## Project Walkthrough
This project will give you a detailed guide to use redux toolkit. An alternative and better way to write Redux logic. It is perfect for someone new to redux, or someone looking for a template to speed up development time. The Readme is well detailed. Be sure to dive into it and the codebase.

#### Docs
[Redux Toolkit Docs](https://redux-toolkit.js.org/introduction/getting-started)
#### Install Template
```sh
npx create-react-app my-app --template redux
```
- @latest
```sh
npx create-react-app@latest my-app --template redux
```
#### Existing App
```sh
npm install @reduxjs/toolkit react-redux
```
#### @reduxjs/toolkit
consists of few libraries
- redux (core library, state management)
- immer (allows to mutate state)
- redux-thunk (handles async actions)
- reselect (simplifies reducer functions)
#### Extras
- redux devtools
- combine reducers
#### react-redux
connects our app to redux
#### Setup Store
- create store.js
```js
import { configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {},
});
```
#### Setup Provider
- index.js
```js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
// import store and provider
import { store } from "./store";
import { Provider } from "react-redux";
ReactDOM.render(
,
document.getElementById("root")
);
```
#### Setup Cart Slice
- application feature
- create features folder/cart
- create cartSlice.js
```js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
cartItems: [],
amount: 0,
total: 0,
isLoading: true,
};
const cartSlice = createSlice({
name: "cart",
initialState,
});
console.log(cartSlice);
export default cartSlice.reducer;
```
- store.js
```js
import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "./features/cart/cartSlice";
export const store = configureStore({
reducer: {
cart: cartReducer,
},
});
```
#### Redux DevTools
- extension
#### Access store value
- create components/Navbar.js
```js
import { CartIcon } from "../icons";
import { useSelector } from "react-redux";
const Navbar = () => {
const { amount } = useSelector((state) => state.cart);
return (
redux toolkit
{amount}
);
};
export default Navbar;
```
#### Hero Icons
- [Hero Icons](https://heroicons.com/)
```css
nav svg {
width: 40px;
color: var(--clr-white);
}
```
#### Setup Cart
- cartSlice.js
```js
import cartItems from "../../cartItems";
const initialState = {
cartItems: cartItems,
amount: 0,
total: 0,
isLoading: true,
};
```
- create CartContainer.js and CartItem.js
- CartContainer.js
```js
import React from "react";
import CartItem from "./CartItem";
import { useSelector } from "react-redux";
const CartContainer = () => {
const { cartItems, total, amount } = useSelector((state) => state.cart);
if (amount < 1) {
return (
{/* cart header */}
your bag
is currently empty
);
}
return (
{/* cart header */}
your bag
{/* cart items */}
{cartItems.map((item) => {
return ;
})}
{/* cart footer */}
total ${total}
clear cart
);
};
export default CartContainer;
```
- CartItem.js
```js
import React from "react";
import { ChevronDown, ChevronUp } from "../icons";
const CartItem = ({ id, img, title, price, amount }) => {
return (
{title}
${price}
{/* remove button */}
remove
{/* increase amount */}
{/* amount */}
{amount}
{/* decrease amount */}
);
};
export default CartItem;
```
#### First Reducer
- cartSlice.js
- Immer library
```js
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
clearCart: (state) => {
state.cartItems = [];
},
},
});
export const { clearCart } = cartSlice.actions;
```
- create action
```js
const ACTION_TYPE = "ACTION_TYPE";
const actionCreator = (payload) => {
return { type: ACTION_TYPE, payload: payload };
};
```
- CartContainer.js
```js
import React from "react";
import CartItem from "./CartItem";
import { useDispatch, useSelector } from "react-redux";
const CartContainer = () => {
const dispatch = useDispatch();
return (
{
dispatch(clearCart());
}}
>
clear cart
);
};
export default CartContainer;
```
#### Remove, Increase, Decrease
- cartSlice.js
```js
import { createSlice } from "@reduxjs/toolkit";
import cartItems from "../../cartItems";
const initialState = {
cartItems: [],
amount: 0,
total: 0,
isLoading: true,
};
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
clearCart: (state) => {
state.cartItems = [];
},
removeItem: (state, action) => {
const itemId = action.payload;
state.cartItems = state.cartItems.filter((item) => item.id !== itemId);
},
increase: (state, { payload }) => {
const cartItem = state.cartItems.find((item) => item.id === payload.id);
cartItem.amount = cartItem.amount + 1;
},
decrease: (state, { payload }) => {
const cartItem = state.cartItems.find((item) => item.id === payload.id);
cartItem.amount = cartItem.amount - 1;
},
calculateTotals: (state) => {
let amount = 0;
let total = 0;
state.cartItems.forEach((item) => {
amount += item.amount;
total += item.amount * item.price;
});
state.amount = amount;
state.total = total;
},
},
});
export const { clearCart, removeItem, increase, decrease, calculateTotals } =
cartSlice.actions;
export default cartSlice.reducer;
```
- CartItem.js
```js
import React from "react";
import { ChevronDown, ChevronUp } from "../icons";
import { useDispatch } from "react-redux";
import { removeItem, increase, decrease } from "../features/cart/cartSlice";
const CartItem = ({ id, img, title, price, amount }) => {
const dispatch = useDispatch();
return (
{title}
${price}
{/* remove button */}
{
dispatch(removeItem(id));
}}
>
remove
{/* increase amount */}
{
dispatch(increase({ id }));
}}
>
{/* amount */}
{amount}
{/* decrease amount */}
{
if (amount === 1) {
dispatch(removeItem(id));
return;
}
dispatch(decrease({ id }));
}}
>
);
};
export default CartItem;
```
- App.js
```js
import { useEffect } from "react";
import Navbar from "./components/Navbar";
import CartContainer from "./components/CartContainer";
import { useSelector, useDispatch } from "react-redux";
import { calculateTotals } from "./features/cart/cartSlice";
function App() {
const { cartItems } = useSelector((state) => state.cart);
const dispatch = useDispatch();
useEffect(() => {
dispatch(calculateTotals());
}, [cartItems]);
return (
);
}
export default App;
```
#### Modal
- create components/Modal.js
```js
const Modal = () => {
return (
Remove all items from your shopping cart?
confirm
cancel
);
};
export default Modal;
```
- App.js
```js
return (
);
```
#### modal slice
- create features/modal/modalSlice.js
```js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
isOpen: false,
};
const modalSlice = createSlice({
name: "modal",
initialState,
reducers: {
openModal: (state, action) => {
state.isOpen = true;
},
closeModal: (state, action) => {
state.isOpen = false;
},
},
});
export const { openModal, closeModal } = modalSlice.actions;
export default modalSlice.reducer;
```
- App.js
```js
const { isOpen } = useSelector((state) => state.modal);
return (
{isOpen && }
);
```
#### toggle modal
- CartContainer.js
```js
import { openModal } from "../features/modal/modalSlice";
return (
{
dispatch(openModal());
}}
>
clear cart
);
```
- Modal.js
```js
import { closeModal } from "../features/modal/modalSlice";
import { useDispatch } from "react-redux";
import { clearCart } from "../features/cart/cartSlice";
const Modal = () => {
const dispatch = useDispatch();
return (
Remove all items from your shopping cart?
{
dispatch(clearCart());
dispatch(closeModal());
}}
>
confirm
{
dispatch(closeModal());
}}
>
cancel
);
};
export default Modal;
```
#### async functionality with createAsyncThunk
- [Course API](https://course-api.com/)
- https://course-api.com/react-useReducer-cart-project
- cartSlice.js
- action type
- callback function
- lifecycle actions
```js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
const url = "https://course-api.com/react-useReducer-cart-project";
export const getCartItems = createAsyncThunk("cart/getCartItems", () => {
return fetch(url)
.then((resp) => resp.json())
.catch((err) => console.log(error));
});
const cartSlice = createSlice({
name: "cart",
initialState,
extraReducers: {
[getCartItems.pending]: (state) => {
state.isLoading = true;
},
[getCartItems.fulfilled]: (state, action) => {
console.log(action);
state.isLoading = false;
state.cartItems = action.payload;
},
[getCartItems.rejected]: (state) => {
state.isLoading = false;
},
},
});
```
- App.js
```js
import { calculateTotals, getCartItems } from "./features/cart/cartSlice";
function App() {
const { cartItems, isLoading } = useSelector((state) => state.cart);
useEffect(() => {
dispatch(getCartItems());
}, []);
if (isLoading) {
return (
Loading...
);
}
return (
{isOpen && }
);
}
export default App;
```
#### Options
```sh
npm install axios
```
- cartSlice.js
```js
export const getCartItems = createAsyncThunk(
"cart/getCartItems",
async (name, thunkAPI) => {
try {
// console.log(name);
// console.log(thunkAPI);
// console.log(thunkAPI.getState());
// thunkAPI.dispatch(openModal());
const resp = await axios(url);
return resp.data;
} catch (error) {
return thunkAPI.rejectWithValue("something went wrong");
}
}
);
```
#### The extraReducers "builder callback" notation
cart/cartSlice
```js
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
// reducers
},
extraReducers: (builder) => {
builder
.addCase(getCartItems.pending, (state) => {
state.isLoading = true;
})
.addCase(getCartItems.fulfilled, (state, action) => {
// console.log(action);
state.isLoading = false;
state.cartItems = action.payload;
})
.addCase(getCartItems.rejected, (state, action) => {
console.log(action);
state.isLoading = false;
});
},
});
```