Cart Array in useState won't update with onClick Event - javascript

I just began learning React and learning how to use Context Api. I am trying to update my cart when user clicks the add to cart button from the product page but for some reason it won't update the state.
Here is my code:
context api:
import React, { createContext, useEffect, useState } from 'react';
import { detailProduct, storeProducts } from './data';
const ProductContext = createContext();
const ProviderContext = ({ children }) => {
const [products, setProducts] = useState({
product: [],
detailsProduct: detailProduct,
cart: [],
modalOpen: false,
modalProduct: detailProduct,
});
const { product, detailsProduct, cart, modalOpen, modalProduct } = products;
const newProducts = () => {
let tempProducts = [];
storeProducts.forEach((item) => {
const singleItem = { ...item };
tempProducts = [...tempProducts, singleItem];
});
setProducts({
...products,
product: tempProducts,
});
};
useEffect(() => {
newProducts();
}, []);
const getItem = (id) => {
const singleProduct = product.find((item) => item.id === id);
return singleProduct;
};
const handleDetail = (id) => {
const newProduct = getItem(id);
setProducts({ ...products, detailsProduct: newProduct });
};
const addToCart = (id) => {
let tempProducts = [...product];
const index = tempProducts.indexOf(getItem(id));
const cartProduct = tempProducts[index];
cartProduct.inCart = true;
cartProduct.count = 1;
const price = cartProduct.price;
cartProduct.total = price;
setProducts({
...products,
product: tempProducts,
cart: [...cart, cartProduct],
});
console.log(products);
};
const openModal = (id) => {
const product = getItem(id);
setProducts({ ...products, modalProduct: product, modalOpen: true });
console.log(products);
};
const closeModal = () => {
setProducts({ ...products, modalOpen: false });
};
return (
<ProductContext.Provider
value={{
...products,
handleDetail: handleDetail,
addToCart: addToCart,
openModal: openModal,
closeModal: closeModal,
}}
>
{children}
</ProductContext.Provider>
);
};
const ConsumerContext = ProductContext.Consumer;
export { ProviderContext, ConsumerContext };
My product page:
import React, { Fragment } from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { ConsumerContext } from '../Context';
import PropTypes from 'prop-types';
const Product = ({ product }) => {
const { id, title, img, price, inCart } = product;
return (
<ProductWrapper className='col-9 mx-auto col-md-6 col-lg-3 my-3'>
<div className='card'>
<ConsumerContext>
{(value) => (
<div
className='img-container p-5'
onClick={() => value.handleDetail(id)}
>
<Link to='/details'>
<img src={img} alt='product' className='card-img-top' />
</Link>
<button
className='cart-btn'
disabled={inCart ? true : false}
onClick={() => {
value.addToCart(id);
value.openModal(id);
}}
>
{inCart ? (
<p className='text-capitalize mb-0' disabled>
in cart
</p>
) : (
<i className='fas fa-cart-plus'></i>
)}
</button>
</div>
)}
</ConsumerContext>
<div className='card-footer d-flex justify-content-between'>
<p className='align-self-center mb-0'>{title}</p>
<h5 className='tex-blue font-italic mb-0'>
<span className='mr-1'>$</span>
{price}
</h5>
</div>
</div>
</ProductWrapper>
);
};
export default Product;
I am also using the onClick element on the product details page which for some reason works while the one on the product page doesn't work. here is the product details code:
import React from 'react';
import { ConsumerContext } from '../Context';
import { Link } from 'react-router-dom';
import { ButtonContainer } from './Button';
const ProductDetails = () => {
return (
<ConsumerContext>
{(value) => {
const { id, company, img, info, price, title, inCart } =
value.detailsProduct;
return (
<div className='container py-5'>
{/* {title} */}
<div className='row'>
<div className='col-10 mx-auto text-center text-slanted text-blue my-5'>
<h1>{title}</h1>
</div>
</div>
{/* {title} */}
{/* {product info} */}
<div className='row'>
<div className='col-10 mx-auto col-md-6 my3'>
<img src={img} alt='product' className='img-fluid' />
</div>
{/* {product text} */}
<div className='col-10 mx-auto col-md-6 my3 text-capitalize'>
<h2>model : {title}</h2>
<h4 className='text-title text-uppercase text-muted mt-3 mb-2'>
made by : <span className='text-uppercase'>{company}</span>
</h4>
<h4 className='text-blue'>
<strong>
price : <span>$</span>
{price}
</strong>
</h4>
<p className='text-capitalize font-weight-bold mt-3 mb-0'>
about product:
</p>
<p className='text-muted lead'>{info}</p>
{/* {buttons} */}
<div>
<Link to='/'>
<ButtonContainer>back to products</ButtonContainer>
</Link>
<ButtonContainer
cartButton
disabled={inCart ? true : false}
onClick={() => {
value.addToCart(id);
// value.openModal(id);
}}
>
{inCart ? 'in cart' : 'add to cart'}
</ButtonContainer>
</div>
</div>
</div>
</div>
);
}}
</ConsumerContext>
);
};
export default ProductDetails;

Related

How do I use my functions to other page in typescirpt?

This page is "detail.tsx".Mainly is to use "LikeButton" to another pages.
import React, { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/router';
import { GoVerified } from 'react-icons/go';
import Image from 'next/image';
import Link from 'next/link';
import { MdOutlineCancel } from 'react-icons/md';
import { BsFillPlayFill } from 'react-icons/bs';
import { HiVolumeUp, HiVolumeOff } from 'react-icons/hi';
import Comments from '../../components/Comments';
import LikeButton from '../../components/LikeButton';
import useAuthStore from '../../store/authStore';
import { Video } from '../../types';
import axios from 'axios';
interface IProps {
postDetails: Video;
}
const Detail = ({ postDetails }: IProps) => {
const [post, setPost] = useState(postDetails);
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [isVideoMuted, setIsVideoMuted] = useState<boolean>(false);
const [isPostingComment, setIsPostingComment] = useState<boolean>(false);
const [comment, setComment] = useState<string>('');
const videoRef = useRef<HTMLVideoElement>(null);
const router = useRouter();
const { userProfile }: any = useAuthStore();
useEffect(() => {
if (post && videoRef?.current) {
videoRef.current.muted = isVideoMuted;
}
}, [post, isVideoMuted]);
const handleLike = async (like: boolean) => {
if (userProfile) {
const res = await axios.put(`https://.../api/like`, {
userId: userProfile._id,
postId: post._id,
like
});
setPost({ ...post, likes: res.data.likes });
}
};
const addComment = async (e: { preventDefault: () => void }) => {
e.preventDefault();
if (userProfile) {
if (comment) {
setIsPostingComment(true);
const res = await axios.put(`https://.../api/post/${post._id}`, {
userId: userProfile._id,
comment,
});
setPost({ ...post, comments: res.data.comments });
setComment('');
setIsPostingComment(false);
}
}
};
return (
<>
{post && (
<div className='flex w-full absolute left-0 top-0 bg-white flex-wrap lg:flex-nowrap'>
<div className='relative flex-2 w-[1000px] lg:w-9/12 flex justify-center items-center bg-blurred-img bg-no-repeat bg-cover bg-center'>
<div className='opacity-90 absolute top-6 left-2 lg:left-6 flex gap-6 z-50'>
<p className='cursor-pointer ' onClick={() => router.back()}>
<MdOutlineCancel className='text-white text-[35px] hover:opacity-90' />
</p>
</div>
<div className='relative'>
<div className='lg:h-[100vh] h-[60vh]'>
<video
ref={videoRef}
controls
loop
src={post?.video?.asset.url}
className=' h-full cursor-pointer'
></video>
</div>
<div className='absolute top-[45%] left-[40%] cursor-pointer'>
</div>
</div>
<div className='absolute bottom-5 lg:bottom-10 right-5 lg:right-10 cursor-pointer'>
</div>
</div>
<div className='relative w-[1000px] md:w-[900px] lg:w-[700px]'>
<div className='lg:mt-10 mt-10'>
<Link href={`/profile/${post.postedBy._id}`}>
<div className='flex gap-4 mb-4 bg-white w-full pl-10 cursor-pointer'>
<Image
width={60}
height={60}
alt='user-profile'
className='rounded-full'
src={post.postedBy.image}
/>
<div>
<div className='text-xl font-bold captilize tracking-wider flex gap-2 items-center justify-center'>
{post.postedBy.userName.replace(/\s+/g, '')}{' '}
<GoVerified className='text-blue-400 text-xl' />
</div>
<p className='text-md lowercase'> {post.postedBy.userName}</p>
</div>
</div>
</Link>
<div className='px-10'>
<p className=' text-lg text-black-100'>{post.caption}</p>
</div>
<div className='mt-5 px-10'>
{userProfile && <LikeButton
likes={post.likes}
flex='flex'
handleLike={() => handleLike(true)}
handleDislike={() => handleLike(false)}
/>}
</div>
<Comments
comment={comment}
setComment={setComment}
addComment={addComment}
comments={post.comments}
isPostingComment={isPostingComment}
/>
</div>
</div>
</div>
)}
</>
);
};
export {LikeButton};
export const getServerSideProps = async ({
params: { id },
}: {
params: { id: string };
}) => {
const res = await axios.get(`https://.../api/post/${id}`);
return {
props: { postDetails: res.data },
};
};
export default Detail;
// export const LikeButton = 'handlelike';
This is my main "LikeButton.tsx" page which got referred from "detail.tsx" page
import React, { useEffect, useState } from 'react';
import { MdFavorite } from 'react-icons/md';
import { NextPage } from 'next';
import useAuthStore from '../store/authStore';
// import { AiOutlineStar, GiFallingStar } from 'react-icons/gi';
import { BsStar } from 'react-icons/bs';
import { AiFillStar } from 'react-icons/ai';
interface IProps {
likes: any;
flex: string;
handleLike: () => void;
handleDislike: () => void;
}
const LikeButton: NextPage<IProps> = ({ likes, flex, handleLike, handleDislike }) => {
const [alreadyLiked, setAlreadyLiked] = useState(false);
const { userProfile }: any = useAuthStore();
let filterLikes = likes?.filter((item: any) => item._ref === userProfile?._id);
useEffect(() => {
if (filterLikes?.length > 0) {
setAlreadyLiked(true);
} else {
setAlreadyLiked(false);
}
}, [filterLikes, likes]);
return (
<div className={`${flex} gap-6`}>
<div className='mt-4 flex flex-col justify-center items-center cursor-pointer'>
{alreadyLiked ? (
<div className='bg-primary rounded-full p-2 md:p-4 text-[#006ee6] ' onClick={handleDislike} >
<AiFillStar className='text-lg md:text-2xl' />
</div>
) : (
<div className='bg-primary rounded-full p-2 md:p-4 ' onClick={handleLike} >
<AiFillStar className='text-lg md:text-2xl' />
</div>
)}
<p className='text-md font-semibold '>{likes?.length || 0}</p>
</div>
</div>
);
};
export default LikeButton;
I would like to use my "LikeButton" function from "detail.tsx" to another pages (not worked with import and export). Is there any advice to use only "LikeButton" functionally in another pages?

How to render a icon dynamically using React Redux

I'm creating a playlist and I want to show my "favorite tracks" with a different icon, like a heart filled, by default render a bordered heart. Basically the default code is working but when refresh the page, the icon filled is gone and render the default.
And if anyone has a tip, i'm new in react and dont know if I can have many "useStates" like that.
I forgot to mention, the app are using Redux-persist, so the info continues in the store after the page refresh. That is why I want show the icon filled based on info at the store
import React, { useState, useEffect } from "react";
import ReactDOMServer from "react-dom/server";
import axios from "axios";
import { useSelector, useDispatch } from "react-redux";
import { ListContainer, PlayerStyle, InfoBox } from "./style.jsx";
import FavoriteBorderIcon from "#material-ui/icons/FavoriteBorder";
import FavoriteIcon from "#material-ui/icons/Favorite";
import PlayCircleOutlineIcon from "#material-ui/icons/PlayCircleOutline";
import PauseCircleOutlineIcon from "#material-ui/icons/PauseCircleOutline";
import MusicPlayer from "../MusicPlayer/index";
const List = () => {
const [isLoading, setLoading] = useState(true);
const [valueList, setValueList] = useState(20);
const [search, setSearch] = useState("");
const [dataList, setDataList] = useState([]);
const [trackList, setTrackList] = useState([]);
const [bannerAlbum, setBannerAlbum] = useState("");
const [createdBy, setCreatedBy] = useState("");
const [isPlayed, setIsPlayed] = useState(false);
const [musicPlayed, setMusicPlayed] = useState("");
const [showTrackList, setShowTrackList] = useState(true);
const [showFavoriteList, setShowFavoriteList] = useState(false);
const [favoriteData, setFavoriteData] = useState([]);
const favoriteList = useSelector(
(state) => state.FavoriteListReducer.favorites
);
const dispatch = useDispatch();
let chartPlaylist = `https://api.deezer.com/playlist/1111141961?&limit=${valueList}`;
useEffect(() => {
axios.get(chartPlaylist).then((res) => {
// console.log(res.data, "album");
setDataList(res.data);
setTrackList(res.data.tracks.data);
setBannerAlbum(res.data.picture_medium);
setCreatedBy(res.data.creator.name);
// Ao terminar a requisição dos dados, mostra os componentes
setLoading(false);
});
}, [chartPlaylist]);
useEffect(() => {
axios.all(favoriteList.map((l) => axios.get(l))).then(
axios.spread(function (...res) {
// all requests are now complete
setFavoriteData(res);
})
);
if (!showTrackList && favoriteList.length === 0) {
setShowTrackList(true);
setShowFavoriteList(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [favoriteList]);
const handleInput = (e) => {
setSearch(e.target.value);
};
const handlePlayed = () => {
if (!isPlayed) {
setIsPlayed(true);
} else {
setIsPlayed(false);
}
};
const handleIconSwap = (e) => {
e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
!isPlayed ? <PlayCircleOutlineIcon /> : <PauseCircleOutlineIcon />
);
};
const Add_fav = (e) => {
dispatch({
type: "ADD_FAV",
newId: `https://api.deezer.com/track/${e.currentTarget.getAttribute(
"value"
)}`,
});
e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
favoriteList.includes(e.currentTarget.getAttribute("value")) ? (
""
) : (
<FavoriteIcon style={{ color: "red" }} />
)
);
};
const Del_fav = (e) => {
dispatch({
type: "DELETE_FAV",
remove: `https://api.deezer.com/track/${e.currentTarget.getAttribute(
"value"
)}`,
});
e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
favoriteList.includes(e.currentTarget.getAttribute("value")) ? (
""
) : (
<FavoriteBorderIcon fontSize={"inherit"} />
)
);
};
// eslint-disable-next-line no-array-constructor
const handleFav = (e) => {
favoriteList.includes(
`https://api.deezer.com/track/${e.currentTarget.getAttribute("value")}`
)
? Del_fav(e)
: Add_fav(e);
};
const toggleData = () => {
if (showTrackList && favoriteList.length > 0) {
setShowTrackList(false);
setShowFavoriteList(true);
}
if (!showTrackList) {
setShowTrackList(true);
setShowFavoriteList(false);
}
};
if (isLoading) {
return <div>Loading...</div>;
}
return (
<>
<button
onClick={() => {
if (valueList < 100) {
setValueList(valueList + 20);
}
}}
>
adicionar +20 musicas
</button>
<br />
<br />
<br />
<button
onClick={() => {
toggleData();
}}
>
Mostrar Favoritos
</button>
<InfoBox>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<div className="content">
<img src={bannerAlbum} alt="" />
<div className="Info--desc">
<h1>{dataList.title}</h1>
<div>
<span>criado por: </span>
{createdBy}
</div>
</div>
</div>
</InfoBox>
<ListContainer>
<div className="headerList">
<div className="rank">#</div>
<div className="favorite--icon">
{/* <FavoriteBorderIcon fontSize={"inherit"} /> */}
</div>
<div className="title--music">FAIXA</div>
<div className="title--artist">ARTISTA</div>
<div className="title--album">ALBUM</div>
<div className="title--duration">D.</div>
</div>
<div className="bodyList">
{showTrackList &&
trackList.map((item, key) => {
return (
<>
<div
key={key}
onMouseEnter={handleIconSwap}
onMouseLeave={(e) => {
if (!isPlayed) {
e.currentTarget.firstElementChild.innerHTML = key + 1;
} else {
e.currentTarget.firstElementChild.innerHTML =
ReactDOMServer.renderToString(
<PauseCircleOutlineIcon />
);
}
}}
>
<div
className="rank"
onClick={() => setMusicPlayed(trackList[key].preview)}
>
{key + 1}
</div>
<div className="favorite--icon">
// Here that has to dynamically render
<span value={item.id} onClick={(e) => handleFav(e)}>
<Icon favIco={false} />
</span>
</div>
<div className="title--music">
<a target="_blank" rel="noreferrer" href={item.link}>
{item.title}
</a>
</div>
<div className="title--artist">{item.artist.name}</div>
<div className="title--album">{item.album.title}</div>
<div className="title--duration">
{item.duration / 60 < 10
? "0" +
(item.duration / 60)
.toFixed(2)
.toString()
.replace(".", ":")
: (item.duration / 60)
.toFixed(2)
.toString()
.replace(".", ":")}
</div>
</div>
</>
);
})}
</div>
</ListContainer>
</>
);
};
const Icon = (props) => {
switch (props.favIco) {
case true:
return <FavoriteIcon style={{ color: "red" }} />;
case false:
return <FavoriteBorderIcon fontSize={"inherit"} />;
default:
return <FavoriteBorderIcon fontSize={"inherit"} />;
}
};
export default List;
My Reducer store is working fine, just not render the heart filled on load the page, if I click at heart and remove the info from the store and click again the icon change to filled again.
const initialState = {
favorites: [],
};
const List = (state = initialState, action) => {
switch (action.type) {
case "ADD_FAV":
return { ...state, favorites: [...state.favorites, action.newId] };
case "DELETE_FAV":
let index = state.favorites.indexOf(action.remove);
if (index > -1) {
state.favorites.splice(index, 1);
}
return {
...state,
favorites: [...state.favorites],
};
default:
}
return state;
};
export default List;
Thank you all
I've seen in your comment above that you're currently using redux-persist to keep the store between reloads, so having access to the data is not the problem.
Looking your code I think the problem might be with the way you're rendering the icons.
If I understood correctly, that is done by the Add_fav function:
const Add_fav = (e) => {
dispatch({
type: "ADD_FAV",
newId: `https://api.deezer.com/track/${e.currentTarget.getAttribute(
"value"
)}`,
});
e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
favoriteList.includes(e.currentTarget.getAttribute("value")) ? (
""
) : (
<FavoriteIcon style={{ color: "red" }} />
)
);
};
But this function only runs when you add a new favorite - hence the absence of the icons after a reload, because this part of the code would not be ran at all.
My suggestion would be to change the icon rendering logic to the return JSX of the component, instead of inside the event handler function. Something like this:
<span value={item.id} onClick={(e) => handleFav(e)}>
{
favoriteList.includes(item.id)
? <FavoriteIcon style={{ color: "red" }} />
: <FavoriteBorderIcon fontSize={"inherit"} />
}
</span>
So figured out I was send to the store was a string and at the render logic was searching for a int number, then a edited the function add the method parseInt()
const Add_fav = (e) => {
dispatch({
type: "ADD_FAV",
newId: parseInt(e.currentTarget.getAttribute("value")),
});
};
and now works fine bacause the logic is searching for a Number Int.
<span value={item.id} onClick={(e) => handleFav(e)}>
{
favoriteList.includes(item.id)
? <FavoriteIcon style={{ color: "red" }} />
: <FavoriteBorderIcon fontSize={"inherit"} />
}
</span>

How to update quantity when item is added to cart rather than add same item twice - Context API - Reaxt

I only have one item for my basket app, when I add it multiple times it adds multiple different basketItems all with same id and with same quantity so for example 3 items all of quantity 3. What I want however is for there just to be one item with a quantity of 3.
I also want to be able to remove the item from cart by quantity - so one at a time. however currently when I click to remove from cart it removes it entirely from cart even if quantity in cart is more than 1.
How do I do this?
code sandbox [here] (https://codesandbox.io/s/silly-jepsen-ix3dd?file=/src/pages/ProductDetailsPage.js&resolutionWidth=584&resolutionHeight=696)
code below:
CartReducer.js
import {ADD_TO_CART, CHANGE_CART_QUANTITY, DECREASE, REMOVE_FROM_CART} from '../Types'
export const CartReducer = (state, action) => {
switch (action.type) {
case ADD_TO_CART: {
return {
...state,
cart: [...state.cart, { ...action.payload, qty : 1}]
};
}
case REMOVE_FROM_CART: {
return {
...state,
cart: state.cart.filter((c) => c.id !== action.payload.id,)
};
}
default:
return state
}
}
CartContext.js
import { createContext, useContext, useReducer } from "react";
import { CartReducer } from "./CartReducer";
import { products } from "../../pages/ProductDetailsPage";
const Cart = createContext();
const Context = ({ children }) => {
const [state, dispatch] = useReducer(CartReducer, {
products: products,
cart: [],
});
// const [productState, productDispatch] = useReducer(productReducer, {
// byStock: false,
// byFastDelivery: false,
// byRating: 0,
// searchQuery: "",
// });
// console.log(productState);
return (
<Cart.Provider value={{ state, dispatch }}>
{children}
</Cart.Provider>
);
};
export const CartState = () => {
return useContext(Cart);
};
export default Context;
BasketItem.js
import React, { useContext } from 'react'
import image from '../../assets/image.png'
// import { QuantityButtonDiv } from '../components/QuantityButtonDiv'
import plusButtonImage from '../../assets/vector+.png'
import subtractButtonImage from '../../assets/vector.png'
import { CartState } from '../../context/cart/CartContext'
import { ADD_TO_CART, DECREASE, REMOVE_FROM_CART } from '../../context/Types'
import CustomizedSelect from '../SelectInput'
export const BasketItem = ({item}) => {
// const { cartItems, removeItem } = useContext(CartContext);
const {
state: { cart },
dispatch,
} = CartState();
return (
<div className="basket-item">
<div className="title-div">
<span>
{item.title}
</span>
</div>
<div className="image-div">
<img style={{height: "100%", width: "100%"}} src={image}/>
</div>
<div className="price-div">
<span>
£{item.price}
</span>
</div>
<div className="basket-quantity-div">
<button onClick={() => dispatch({
type: REMOVE_FROM_CART,
payload: item,
})} className="subtract-btn">
<img src={subtractButtonImage}/>
</button>
<span className="quantity-value">
{cart.length}
</span>
<button onClick={() => dispatch({
type: ADD_TO_CART,
payload: item,
})} className="add-btn">
<img src={plusButtonImage}/>
</button>
</div>
<div className="total-div">
£{cart.reduce((amount, item) => item.price + amount, 0)}
</div>
</div>
)
}
Product.js
import React, { useContext, useState } from 'react'
import image from '../../assets/image.png'
import { QuantityButtonDiv } from '../QuantityButtonDiv'
import {BasketItem} from '../basketItem/BasketItem'
import { CartContext, CartState } from '../../context/cart/CartContext'
import { ADD_TO_CART, REMOVE_FROM_CART } from '../../context/Types'
export const Product = ({product}) => {
// const {addToCart, cartItems, removeItem } = useContext(CartContext)
const { state: {cart}, dispatch } = CartState();
const [stockCount, setStockCount] = useState(10)
return (
<div>
<div className="image-div">
<img style={{height: "100%", width: "100%"}} src={image}/>
</div>
<div className="details-div">
<h1>{product.title}</h1>
<span>
{product.description}
</span>
<span className="price">
£ {product.price}
</span>
<div className="stock-div">
{stockCount} in stock
</div>
<QuantityButtonDiv/>
{cart.some((p) => p.id === product.id) ? (
//checking to see if item is in cart if so remove from cart button appears
<button onClick={() => dispatch({
type: REMOVE_FROM_CART,
payload: product,
})} className="remove-button">
Remove From Cart
</button>
) : (
<></>
)}
<button onClick={() => {dispatch({
type: ADD_TO_CART,
payload: product,
}); setStockCount(stockCount-1)}} disable={stockCount <= 0} className="add-to-cart-button">
{stockCount === 0 ? "Out Of Stock" : "Add To Cart"}
</button>
</div>
</div>
)
}
before adding new product to cart, you need to check if the product is already in the cart. You need to check it in ADD_TO_CART case.
in REMOVE_FROM_CART case, check if the quantity is more than 1. If its more than 1, don't remove it.

Checkbox isChecked doesn't affect the price

I've two different prices,
when i check the checkbox for second price,
the state show and display the second price properly
but when i press the pay button of that product with second price, it will send the first price
instead of what I've checked which was second price.
so i want increment both prices separately when i checked the check box and
it sends the specific price to the payment process
import React, { useState, useEffect } from "react";
import {
getProducts,
getBraintreeClientToken,
processPayment,
createOrder
} from "./apiCore";
import { emptyCart } from "./cartHelpers";
import { isAuthenticated } from "../auth";
import { Link } from "react-router-dom";
import "braintree-web";
import DropIn from "braintree-web-drop-in-react";
import { makeStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
width: '25ch',
},
},
}));
const Checkout = ({ products, setRun = f => f, run = undefined }) => {
const classes = useStyles();
const [data, setData] = useState({
loading: false,
success: false,
clientToken: null,
error: "",
instance: {},
address: "",
mobile:""
});
>>>>>> const [price, setPrice]= useState(() => () => getTotal())
>>>>>> const [isChecked, setIsChecked]= useState(false);
const userId = isAuthenticated() && isAuthenticated().user._id;
const token = isAuthenticated() && isAuthenticated().token;
const getToken = (userId, token) => {
getBraintreeClientToken(userId, token).then(data => {
if (data.error) {
setData({ ...data, error: data.error });
} else {
setData({ clientToken: data.clientToken });
}
});
};
useEffect(() => {
getToken(userId, token);
}, []);
const handleAddress = event => {
setData({ ...data, address: event.target.value });
};
const handleMobile = event => {
setData({ ...data, mobile: event.target.value });
};
>>>>>> const getTotal = () => {
>>>>>> return products.reduce((currentValue, nextValue) => {
>>>>>> return currentValue + nextValue.count * nextValue.price;
>>>>>>
>>>>>> }, 0);
>>>>>> };
>>>>>> const getTotal2 = () => {
>>>>>> return products.reduce((currentValue, nextValue) => {
>>>>>> return currentValue + nextValue.count * nextValue.price2;
>>>>>> }, 0);
>>>>>> };**
const showCheckout = () => {
return isAuthenticated() ? (
<div>{showDropIn()}</div>
) : (
<Link to="/signin">
<button className="btn btn-primary">Sign in to checkout</button>
</Link>
);
};
let deliveryAddress = data.address
let deliveryMobile = data.mobile
const buy = () => {
setData({ loading: true });
// send the nonce to your server
// nonce = data.instance.requestPaymentMethod()
let nonce;
let getNonce = data.instance
.requestPaymentMethod()
.then(data => {
// console.log(data);
nonce = data.nonce;
// once you have nonce (card type, card number) send nonce as 'paymentMethodNonce'
// and also total to be charged
// console.log(
// "send nonce and total to process: ",
// nonce,
// getTotal(products)
// );
>>>>>> **const paymentData = {
>>>>>> paymentMethodNonce: nonce,
>>>>>> amount: getTotal(products)
>>>>>> };**
processPayment(userId, token, paymentData)
.then(response => {
console.log(response);
// empty cart
// create order
const createOrderData = {
products: products,
transaction_id: response.transaction.id,
amount: response.transaction.amount,
address: deliveryAddress,
mobile: deliveryMobile
};
createOrder(userId, token, createOrderData)
.then(response => {
emptyCart(() => {
setRun(!run); // run useEffect in parent Cart
console.log(
"payment success and empty cart"
);
setData({
loading: false,
success: true
});
});
})
.catch(error => {
console.log(error);
setData({ loading: false });
});
})
.catch(error => {
console.log(error);
setData({ loading: false });
});
})
.catch(error => {
// console.log("dropin error: ", error);
setData({ ...data, error: error.message });
});
};
const showDropIn = () => (
<div onBlur={() => setData({ ...data, error: "" })}>
{data.clientToken !== null && products.length > 0 ? (
<div>
<div className="gorm-group mb-3">
<label className="text-muted">Delivery address:</label>
<textarea
onChange={handleAddress}
className="form-control"
value={data.address}
placeholder="Type your delivery address here..."
/>
</div>
<div className="gorm-group mb-3">
<label className="text-muted">mobile number:</label>
<form className={classes.root} noValidate autoComplete="off">
<TextField placeholder="mobile number" onChange={handleMobile} type="text" value={data.mobile} id="outlined-basic" label="mobile" variant="outlined" />
</form>
</div>
<DropIn
options={{
authorization: data.clientToken,
paypal: {
flow: "vault"
}
}}
onInstance={instance => (data.instance = instance)}
/>
<button onClick={buy} className="btn btn-success btn-block">
Pay
</button>
</div>
) : null}
</div>
);
const showError = error => (
<div
className="alert alert-danger"
style={{ display: error ? "" : "none" }}
>
{error}
</div>
);
const showSuccess = success => (
<div
className="alert alert-info"
style={{ display: success ? "" : "none" }}
>
Thanks! Your payment was successful!
</div>
);
const showLoading = loading =>
loading && <h2 className="text-danger">Loading...</h2>;
return (
<div>
>>>>>> **<form>
>>>>>> <h1>قیمت کل: {isChecked ? getTotal2() : getTotal()} </h1>
>>>>>> <label>نیم لیتر :</label>
>>>>> <input type="checkbox"
>>>>>> checked={isChecked}
>>>>>> onChange={(e)=>{setIsChecked(e.target.checked)}}/>
>>>>>> </form>**
{showLoading(data.loading)}
{showSuccess(data.success)}
{showError(data.error)}
{showCheckout()}
</div>
);
};
export default Checkout;
my card component
import React, { useState, version,useEffect } from 'react';
import { Link, Redirect } from 'react-router-dom';
import ShowImage from './ShowImage';
import moment from 'moment';
import { addItem, updateItem, removeItem } from './cartHelpers';
import logo from '../images/logo.svg'
//material ui
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Grid from '#material-ui/core/Grid'
import { Typography } from '#material-ui/core';
import CardHeader from '#material-ui/core/CardHeader';
import CardMedia from '#material-ui/core/CardMedia';
import CardActions from '#material-ui/core/CardActions';
import Collapse from '#material-ui/core/Collapse';
import Avatar from '#material-ui/core/Avatar';
import IconButton from '#material-ui/core/IconButton';
import MoreVertIcon from '#material-ui/icons/MoreVert';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
import { create } from 'jss';
import rtl from 'jss-rtl';
import { StylesProvider, jssPreset } from '#material-ui/core/styles';
const jss = create({ plugins: [...jssPreset().plugins, rtl()] });
const useStyles = makeStyles((theme) => ({
stock: {
marginBottom: '1rem'
},
Button: {
...theme.typography.button,
padding: '.3rem',
minWidth: 10,
},
media: {
height: 0,
paddingTop: '56.25%', // 16:9
},
title: {
backgroundColor: '#D8D8D8'
},
grid: {
padding: '0'
},
cardFont:{
...theme.typography.body,
textAlign:'right',
marginTop:'.8rem',
marginRight:'1rem'
},productName:{
...theme.typography.body,
textAlign:'right',
},
cardButton:{
marginLeft:'1.4rem'
}
}));
const Cardd = ({
product,
showViewProductButton = true,
showAddToCartButton = true,
cartUpdate = false,
showRemoveProductButton = false,
setRun = f => f,
run = undefined
// changeCartSize
}) => {
const [redirect, setRedirect] = useState(false);
const [count, setCount] = useState(product.count);
//material ui
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
// console.log(price)
const classes = useStyles()
const showViewButton = showViewProductButton => {
return (
showViewProductButton && (
<Link to={`/product/${product._id}`} className="mr-2">
<Button className={classes.Button} size="small" variant="contained">مشاهده محصول</Button>
</Link>
)
);
};
const addToCart = () => {
// console.log('added');
addItem(product, setRedirect(true));
};
const shouldRedirect = redirect => {
if (redirect) {
return <Redirect to="/cart" />;
}
};
const showAddToCartBtn = showAddToCartButton => {
return (
showAddToCartButton && (
<Button className={classes.Button} size="small" onClick={addToCart} variant="contained" color="primary" disableElevation>
اضافه کردن به سبد
</Button>
)
);
};
const showStock = quantity => {
return quantity > 0 ? (
<Button disabled size="small" className={classes.stock}>موجود </Button>
) : (
<Button className={classes.stock}>ناموجود </Button>
);
};
const handleChange = productId => event => {
setRun(!run); // run useEffect in parent Cart
setCount(event.target.value < 1 ? 1 : event.target.value);
if (event.target.value >= 1) {
updateItem(productId, event.target.value);
}
};
const showCartUpdateOptions = cartUpdate => {
return (
cartUpdate && (
<div>
<div className="input-group mb-3">
<div className="input-group-prepend">
<span className="input-group-text">تعداد</span>
</div>
<input type="number" className="form-control" value={count} onChange={handleChange(product._id)} />
</div>
</div>
)
);
};
const showRemoveButton = showRemoveProductButton => {
return (
showRemoveProductButton && (
<Button className={classes.Button} size="small" variant="contained" color="secondary" disableElevation
onClick={() => {
removeItem(product._id);
setRun(!run); // run useEffect in parent Cart
}}
>
حذف محصول
</Button>
)
);
};
return (
<Grid container direction='column' >
<Grid item >
<Card style={{margin:'1rem'}}>
<Grid item className={classes.title}>
<Typography className={classes.productName} variant='h6'>
{product.name}
</Typography>
</Grid>
<CardHeader
avatar={
<Avatar alt="Remy Sharp" src={logo} className={classes.green}>
</Avatar>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title="روغنک"
subheader="September 14, 2016"
/>
<CardContent className={classes.grid} >
{shouldRedirect(redirect)}
<ShowImage item={product} url="product" />
<StylesProvider jss={jss}>
<Typography className={classes.cardFont} variant="body2" component="p">
{product.description.substring(0, 100)}
</Typography>
</StylesProvider>
<div>
<Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
Open Menu
</Button>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</Menu>
</div>
<Typography className={classes.cardFont} variant="body2" component="p">
$یک لیتر {product.price}
</Typography>
<Typography className={classes.cardFont} variant="body2" component="p">
$نیم لیتر {product.price2}
</Typography>
<Typography className={classes.cardFont} variant="body2" component="p">
Category: {product.category && product.category.name}
</Typography>
<Typography className={classes.cardFont} variant="body2" component="p">
Added on {moment(product.createdAt).fromNow()}
</Typography>
<Typography className={classes.cardFont} variant="body2" component="p">
{showStock(product.quantity)}
</Typography>
<br />
<Grid container >
<Grid item className={classes.cardButton} >
{showViewButton(showViewProductButton)}
</Grid>
<Grid item className={classes.cardButton}>
{showAddToCartBtn(showAddToCartButton)}
</Grid>
<Grid item className={classes.cardButton}>
{showRemoveButton(showRemoveProductButton)}
</Grid>
</Grid>
{showCartUpdateOptions(cartUpdate)}
</CardContent>
</Card>
</Grid>
</Grid>
// <div className="card ">
// <div className="card-header card-header-1 ">{product.name}</div>
// <div className="card-body">
// {shouldRedirect(redirect)}
// <ShowImage item={product} url="product" />
// <p className="card-p mt-2">{product.description.substring(0, 100)} </p>
// <p className="card-p black-10">$ {product.price}</p>
// <p className="black-9">Category: {product.category && product.category.name}</p>
// <p className="black-8">Added on {moment(product.createdAt).fromNow()}</p>
// {showStock(product.quantity)}
// <br />
// {showViewButton(showViewProductButton)}
// {showAddToCartBtn(showAddToCartButton)}
// {showRemoveButton(showRemoveProductButton)}
// {showCartUpdateOptions(cartUpdate)}
// </div>
// </div>
);
};
export default Cardd;
CartHelper component
export const addItem = (item = [], count = 0, next = f => f) => {
let cart = [];
if (typeof window !== 'undefined') {
if (localStorage.getItem('cart')) {
cart = JSON.parse(localStorage.getItem('cart'));
}
cart.push({
...item,
count: 1
});
// remove duplicates
// build an Array from new Set and turn it back into array using Array.from
// so that later we can re-map it
// new set will only allow unique values in it
// so pass the ids of each object/product
// If the loop tries to add the same value again, it'll get ignored
// ...with the array of ids we got on when first map() was used
// run map() on it again and return the actual product from the cart
cart = Array.from(new Set(cart.map(p => p._id))).map(id => {
return cart.find(p => p._id === id);
});
localStorage.setItem('cart', JSON.stringify(cart));
next();
}
};
export const itemTotal = () => {
if (typeof window !== 'undefined') {
if (localStorage.getItem('cart')) {
return JSON.parse(localStorage.getItem('cart')).length;
}
}
return 0;
};
export const getCart = () => {
if (typeof window !== 'undefined') {
if (localStorage.getItem('cart')) {
return JSON.parse(localStorage.getItem('cart'));
}
}
return [];
};
export const updateItem = (productId, count) => {
let cart = [];
if (typeof window !== 'undefined') {
if (localStorage.getItem('cart')) {
cart = JSON.parse(localStorage.getItem('cart'));
}
cart.map((product, i) => {
if (product._id === productId) {
cart[i].count = count;
}
});
localStorage.setItem('cart', JSON.stringify(cart));
}
};
export const removeItem = productId => {
let cart = [];
if (typeof window !== 'undefined') {
if (localStorage.getItem('cart')) {
cart = JSON.parse(localStorage.getItem('cart'));
}
cart.map((product, i) => {
if (product._id === productId) {
cart.splice(i, 1);
}
});
localStorage.setItem('cart', JSON.stringify(cart));
}
return cart;
};
export const emptyCart = next => {
if (typeof window !== 'undefined') {
localStorage.removeItem('cart');
next();
}
};

Cannot read property 'map' of undefined in React Redux?

we are currently developing an e-commerce website using react-redux as front-end. It's working fine while passing the local json data using json-server for mock test but while real api url is passed then occur this TypeError: Cannot read property 'map' of undefined error. Please any one support me to solve this problem.
right-sidebar.jsx:
import React, { Component } from "react";
import Slider from "react-slick";
import "../common/index.scss";
import { connect } from "react-redux";
// import custom Components
import Service from "./common/service";
import BrandBlock from "./common/brand-block";
import NewProduct from "../common/new-product";
import Breadcrumb from "../common/breadcrumb";
import DetailsWithPrice from "./common/product/details-price";
import DetailsTopTabs from "./common/details-top-tabs";
import { addToCart, addToCartUnsafe, addToWishlist } from "../../actions";
import ImageZoom from "./common/product/image-zoom";
import SmallImages from "./common/product/small-image";
class RightSideBar extends Component {
constructor() {
super();
this.state = {
nav1: null,
nav2: null,
};
}
componentDidMount() {
this.setState({
nav1: this.slider1,
nav2: this.slider2,
});
}
render() {
const {
symbol,
item,
addToCart,
addToCartUnsafe,
addToWishlist,
} = this.props;
console.log(item);
var products = {
slidesToShow: 1,
slidesToScroll: 1,
dots: false,
arrows: true,
fade: true,
};
var productsnav = {
slidesToShow: 3,
swipeToSlide: true,
arrows: false,
dots: false,
focusOnSelect: true,
};
return (
<div>
{/* <Breadcrumb title={" Product / " + item.name} /> */}
{/*Section Start*/}
{item ? (
<section className="section-b-space">
<div className="collection-wrapper">
<div className="container">
<div className="row">
<div className="col-lg-9 col-sm-12 col-xs-12">
<div className="container-fluid">
<div className="row">
<div className="col-xl-12">
<div className="filter-main-btn mb-2">
<span className="filter-btn">
<i
className="fa fa-filter"
aria-hidden="true"
></i>{" "}
filter
</span>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-6 product-thumbnail">
<Slider
{...products}
asNavFor={this.state.nav2}
ref={(slider) => (this.slider1 = slider)}
className="product-slick"
>
{item.variants.map((vari, index) => (
<div key={index}>
<ImageZoom
image={vari.images}
className="img-fluid image_zoom_cls-0"
/>
</div>
))}
</Slider>
<SmallImages
item={item}
settings={productsnav}
navOne={this.state.nav1}
/>
</div>
<DetailsWithPrice
symbol={symbol}
item={item}
navOne={this.state.nav1}
addToCartClicked={addToCart}
BuynowClicked={addToCartUnsafe}
addToWishlistClicked={addToWishlist}
/>
</div>
</div>
<DetailsTopTabs item={item} />
</div>
<div className="col-sm-3 collection-filter">
{/* <BrandBlock/> */}
<Service />
{/*side-bar single product slider start*/}
<NewProduct />
{/*side-bar single product slider end*/}
</div>
</div>
</div>
</div>
</section>
) : (
""
)}
{/*Section End*/}
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
let productId = ownProps.match.params.id;
return {
item: state.data.products.find((el) => el.id == productId),
symbol: state.data.symbol,
};
};
export default connect(mapStateToProps, {
addToCart,
addToCartUnsafe,
addToWishlist,
})(RightSideBar);
shop.js:
/**
* Mocking client-server processing
*/
import axios from "axios";
// import _products from './data.json'
import React, { useState, useEffect } from "react";
import store from "../store";
import { receiveProducts } from "../actions/index";
const TIMEOUT = 100;
const _products = axios
.get(`http://eversoftgroup.ddns.net:8000/apps/product/`)
.then((response) => {
return response.data;
});
const _category = axios
.get(`http://localhost:4000/categories`)
.then((response) => {
return response.data;
});
export default {
getProducts: (cb, timeout) =>
setTimeout(() => cb(_products), timeout || TIMEOUT),
getCategories: (cb, timeout) =>
setTimeout(() => cb(_category), timeout || TIMEOUT),
buyProducts: (payload, cb, timeout) =>
setTimeout(() => cb(), timeout || TIMEOUT),
};

Categories