After a week of learning and working with Next.JS. I had to build a simple application using the topic is a shopping cart in e-commerce. A lot of different knowledge in the framework when I deep down to learn, because I tried to compare MERN and NEXT.JS. I knew it was wrong but I did. Anyone will do like that - bring the old things to a new house. Something is beautiful and something is stuff.
And something stuff which I got in this framework. One of them that is global windows variable is not ready in all the time
- that means the client-side and server-side are in black and white.
That's why I need some third party:
-
js-cookie
to manage the resource in client-side. -
next-redux-wrapper
to manage the state in the client-side. -
redux
andetc...
First of all
I need create the next application and add third party to project
create-next-app next-simple-shopping && cd next-simple-shopping
yarn add js-cookie next-redux-wrapper react-redux redux redux-devtools-extension redux-thunk
πͺ Setup the cookie to application
// ./libs/useCookie.js
import jsCookie from "js-cookie";
export function getCookie(key) {
let result = [];
if (key) {
const localData = jsCookie.get(key);
if (localData && localData.length > 0) {
result = JSON.parse(localData);
}
}
return result;
}
export function setCookie(key, value) {
jsCookie.set(key, JSON.stringify(value));
}
// cookie ready to serve
π‘ Setup the redux to make the magic in the client side
Initial the store component in redux
// ./store/index.js
import { createStore, applyMiddleware, combineReducers } from "redux";
import { HYDRATE, createWrapper } from "next-redux-wrapper";
import thunkMiddleware from "redux-thunk";
import shopping from "./shopping/reducer";
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== "production") {
const { composeWithDevTools } = require("redux-devtools-extension");
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const combinedReducer = combineReducers({
shopping,
});
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
const initStore = () => {
return createStore(reducer, bindMiddleware([thunkMiddleware]));
};
export const wrapper = createWrapper(initStore);
Also, we need the action
and reducer
in the application, too.
Action of shopping cart
// ./libs/shopping/action.js
export const actionShopping = {
ADD: "ADD",
CLEAR: "CLEAR",
FETCH: "FETCH",
};
export const addShopping = (product) => (dispatch) => {
return dispatch({
type: actionShopping.ADD,
payload: {
product: product,
quantity: 1,
},
});
};
export const fetchShopping = () => (dispatch) => {
return dispatch({
type: actionShopping.FETCH,
});
};
export const clearShopping = () => (dispatch) => {
return dispatch({
type: actionShopping.CLEAR,
});
};
Reducer of shopping cart
// ./libs/shopping/reducer.js
import { getCookie, setCookie } from "../../libs/useCookie";
import { actionShopping } from "./action";
const CARD = "CARD";
const shopInitialState = {
shopping: getCookie(CARD),
};
function clear() {
let shoppings = [];
setCookie(CARD, shoppings);
return shoppings;
}
function removeShoppingCart(data) {
let shoppings = shopInitialState.shopping;
shoppings.filter((item) => item.product.id !== data.product.id);
setCookie(CARD, shoppings);
return shoppings;
}
function increment(data) {
let shoppings = shopInitialState.shopping;
let isExisted = shoppings.some((item) => item.product.id === data.product.id);
if (isExisted) {
shoppings.forEach((item) => {
if (item.product.id === data.product.id) {
item.quantity += 1;
}
return item;
});
}
setCookie(CARD, shoppings);
return shoppings;
}
function decrement(data) {
let shoppings = shopInitialState.shopping;
let isExisted = shoppings.some((item) => item.product.id === data.product.id);
if (isExisted) {
shoppings.forEach((item) => {
if (item.product.id === data.product.id) {
item.quantity -= 1;
}
return item;
});
}
setCookie(CARD, shoppings);
return shoppings;
}
function getShopping() {
return getCookie(CARD);
}
function addShoppingCart(data) {
let shoppings = shopInitialState.shopping;
let isExisted = shoppings.some((item) => item.product.id === data.product.id);
if (isExisted) {
shoppings.forEach((item) => {
if (item.product.id === data.product.id) {
item.quantity += 1;
}
return item;
});
} else {
shoppings.push(data);
}
setCookie(CARD, shoppings);
return shoppings;
}
export default function reducer(state = shopInitialState, action) {
const { type, payload } = action;
switch (type) {
case actionShopping.ADD:
state = {
shopping: addShoppingCart(payload),
};
return state;
case actionShopping.CLEAR:
state = {
shopping: clear(),
};
return state;
case actionShopping.FETCH:
default:
state = {
shopping: getShopping(),
};
return state;
}
}
Okay, the redux is ready to serve π.
Making two
component
for easy manage the state in the client.
> Product component π©³
// ./components/ProductItem.jsx
import React from "react";
import styles from "../styles/Home.module.css";
import Image from "next/image";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { addShopping } from "../store/shopping/action";
const ProductItem = (props) => {
const {
data: { id, name, price, image },
addShopping,
} = props;
return (
<div className={styles.card}>
<Image src={image} alt={name} height="540" width="540" />
<h3>{name}</h3>
<p>{price}</p>
<button onClick={() => addShopping(props.data)}>Add to card</button>
</div>
);
};
const mapDispatchTopProps = (dispatch) => {
return {
addShopping: bindActionCreators(addShopping, dispatch),
};
};
export default connect(null, mapDispatchTopProps)(ProductItem);
> Shopping counter component π
import React, { useEffect, useState } from "react";
import { fetchShopping, clearShopping } from "../store/shopping/action";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
const ShoppingCounter = ({ shopping, fetchShopping, clear }) => {
useEffect(() => {
fetchShopping();
}, []);
return (
<div
style={{
position: "relative",
width: "100%",
textAlign: "right",
marginBottom: "1rem",
}}
>
<h2
style={{
padding: "1rem 1.5rem",
right: "5%",
top: "5%",
position: "absolute",
backgroundColor: "blue",
color: "white",
fontWeight: 200,
borderRadius: "10px",
}}
>
Counter <strong>{shopping}</strong>
<button
style={{
borderRadius: "10px",
border: "none",
color: "white",
background: "orange",
marginLeft: "1rem",
padding: "0.6rem 0.8rem",
outline: "none",
cursor: "pointer",
}}
onClick={clear}
type="button"
>
Clear
</button>
</h2>
</div>
);
};
const mapStateToProps = (state) => {
const data = state.shopping.shopping;
const count =
data.length &&
data
.map((item) => item.quantity)
.reduce((item, current) => {
return item + current;
});
return {
shopping: count,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchShopping: bindActionCreators(fetchShopping, dispatch),
clear: bindActionCreators(clearShopping, dispatch),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ShoppingCounter);
Ops! Don't forget your data mock based on the path to index page
// ./pages/index.js
import { products } from "../mocks/data";
import ShoppingCounter from "../components/ShoppingCounter";
import ProductItem from "../components/ProductItem";
// ...
<ShoppingCounter />
<main className={styles.main}>
<h1 className={styles.title}>Welcome to Next.js shopping π©³!</h1>
<div className={styles.grid}>
{products &&
products.map((product) => (
<ProductItem key={product.id} data={product} />
))}
</div>
</main>
//...
See the live demo simple-shopping-cart
Okay, let's try for yourself. That's is my dev note. Thanks for reading and see you in the next article.
Here is repository
Top comments (13)
It was good if you showed the all code of the index.js file.
For other guys who were stuck on the creation of the store, follow the
Usage
guide: github.com/kirill-konshin/next-red...Long story short, in your
_app.tsx
file:Absolutely, I had edited this article with link repository below the article.
Thanks you for reading.
And I got have a new post deploy express to Vercel. Hope you will read this article.
But this is not an Express application? Are you planning a post to explain how you deployed your shopping cart to Vercel?
Yep, if you want, I will do that for you. Follow me, it's comming in this week.
An upgraded
setCookie
functionThanks you for code.
Thank you,
setCookie
andgetCookie
was halpful for me!You are welcome ππ
Hi Good day
How to display the cart page?
thank you.
Everything has saved in cookie. You can use getCookie function to fetch data and processing with your logic.
Greatππ½
Thanks π
Thanks you for code.
And I got have a new post couponsva. Hope you will read this article.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.