I'm new to react and followed a tutorial to do a shopping cart. In the end, I added a function that would give me the total cost of the products, but I get an error when adding products and clicking on Cart "productList.reduce is not a function". You can see this function (getTotalCost) in Cart. I tried to solve this but I get other errors. These are my files:
ProductsPage :
import React, {useState} from 'react'
import './ProductsPage.css'
import Products from '../../components/Products'
import Cart from '../../components/Cart'
const PAGE_PRODUCTS = 'products';
const PAGE_CART = 'cart';
function ProductsPage() {
const [cart, setCart] = useState ([]);
const [page, setPage] = useState (PAGE_PRODUCTS);
const addToCart = (product) => {
setCart ([...cart, {...product}])
}
const removeFromCart = (productToRemove) => {
setCart(cart.filter(product => product !== productToRemove))
}
const navigateTo = (nextPage) => {
setPage(nextPage);
};
return (
<div className="productspage">
<header>
<button className="cart-btn" onClick={()=> navigateTo(PAGE_CART)}>
Go to Cart ({cart.length})
</button>
<button className="products-btn" onClick={()=> navigateTo(PAGE_PRODUCTS)}>
View Products
</button>
</header>
{page === PAGE_PRODUCTS && <Products addToCart={addToCart}/>}
{page === PAGE_CART && <Cart cart={cart} removeFromCart={removeFromCart} />}
<div>
</div>
</div>
);
};
export default ProductsPage;
Products:
import React, {useState} from 'react'
function Products ({ addToCart }) {
const [products] = useState ([
{
name: 'Breakfast ',
cost:'9.99$',
image: 'https://images.unsplash.com/photo-1569420067112-b57b4f024595?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80',
},
{
name: 'Breakfast box ',
cost:'8.99$',
image: 'https://images.unsplash.com/photo-1569419910356-f63064754fc9?ixlib=rb-1.2.1&auto=format&fit=crop&w=700&q=80',
},
{
name: 'Snack box ',
cost:'6.99$',
image: 'https://images.unsplash.com/photo-1569419882964-7db5d339951b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80',
},
{
name: '4 small breakfast bowls ',
cost:'9.99$',
image: 'https://images.unsplash.com/photo-1570649857669-4ad9f896435d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=703&q=80',
}
])
return (
<>
<h1 className="products-title">Products</h1>
<div className="products">
{products.map((product , index) => (
<div className="product" key={index}>
<h3>{product.name}</h3>
<h4>{product.cost}</h4>
<img src={product.image} alt={product.name}/>
<p></p>
<button onClick={() => addToCart(product)}>
Add to Cart
</button>
</div>
))}
</div>
</>
)
}
export default Products;
Cart
import React from 'react'
import products from '../../components/Products'
function Cart ({ cart, removeFromCart }) {
const getTotalCost = (productList) => (
productList.reduce((totalCost, { cost: itemCost }) => totalCost += parseFloat(itemCost), 0)
);
return (
<>
<h1>Cart</h1>
<div className="products">
{cart.map((product , index) => (
<div className="product" key={index}>
<h3>{product.name}</h3>
<h4>{product.cost}</h4>
<img src={product.image} alt={product.name}/>
<button onClick={() => removeFromCart(product)}>
Remove
</button>
{getTotalCost(products)}
</div>
))}
</div>
</>
)
}
export default Cart;
In the second code snippet, where you are calling {getTotalCost(products)}, you are using the products component you are importing on line 2, which is a Component, not a List, so the reduce function does not exist on it, which is why you're seeing that error.
You probably want {getTotalCost(cart)} instead.
Related
I am trying to delete an item from a shopping cart and I am using the filter hook to accomplish this. I have looked at the documentation for this and at the answers here on stack overflow. unfortunately no luck.
this is my code for the entire component. the function is of course "deleteItemFromBasket" and it is being called at the onclick on the delete button:
function CheckoutProduct({id, title, price, description, rating, category, image }) {
const [basket, addToBasket] = useAppContext();
const deleteItemFromBasket = (id) => {
addToBasket(basket.filter((task) => task.id !== id));
};
return (
<div>
{basket.map((element) => {
if (element === id) {
return (
<div className='grid grid-cols-5 border-b pb-4'>
{/* far left */}
<Image src={image} height={200} width={200} objectFit='contain' />
{/* middle */}
<div className="col-span-3 mx-5">
<p>{title}</p>
<p className='text-xs my-2 line-clamp-3'>{description}</p>
<button onClick={deleteItemFromBasket} className='button'>delete</button>
<h1>items ID in basket: {basket}</h1>
<h1>length of array: {basket.length}</h1>
</div>
{/* right */}
<div>
<p>${price}</p>
</div>
</div>
)
}
})}
</div>
)
}
here is the code of the context provider
import React, { createContext, useContext, useState } from 'react';
const AppContext = createContext();
export function AppWrapper({ children }) {
var [basket, addToBasket]= useState([]);
return (
<AppContext.Provider value={[basket, addToBasket]}>
{children}
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
looks like basket is an array of ids, not an array of objects. Assuming that is the case, then you need to change your delete function to
const deleteItemFromBasket = (id) => {
addToBasket(basket.filter((element) => element !== id));
};
This is assuming that addToBasket is actually just setting the basket, not just additive.
I eventually got it to work and it took a combination of the two answers so thank you everyone for the help!
notice below that the function "deleteItemFromBasket" is the same as joe's post but it to get it to work I needed to add the function in the onClick like zehan's answer. I don't know why this works so if anyone has an explanation as to why I'll save the answer spot for it! thanks friends
import React from 'react';
import Image from 'next/image'
import { useAppContext } from '../state'
function CheckoutProduct({id, title, price, description, rating, category, image }) {
const [basket, addToBasket] = useAppContext();
const deleteItemFromBasket = (item) => {
addToBasket((current) => current.filter((element) => element !== item));
};
return (
<div>
{basket.map((element) => {
if (element === id) {
return (
<div className='grid grid-cols-5 border-b pb-4'>
{/* far left */}
<Image src={image} height={200} width={200} objectFit='contain' />
{/* middle */}
<div className="col-span-3 mx-5">
<p>{title}</p>
<p className='text-xs my-2 line-clamp-3'>{description}</p>
{/* <button onClick={deleteItemFromBasket(element)} >delete item</button> */}
<button onClick={ ()=> deleteItemFromBasket(id)} className='button'>delete</button>
<h1>items ID in basket: {basket}</h1>
<h1>length of array: {basket.length}</h1>
</div>
{/* right */}
<div>
<p>${price}</p>
</div>
</div>
)
}
})}
</div>
)
}
I have a cart. I'm adding item to cart on click addToCart. However, it adds a duplicates of the same items, and not increasing it's quantity in cart. I also have an incrementCount and decrementCount for updating quantity of added items in cart. I want to make a function which will both add to cart and update the quantity in cart if item is the same
I also have a problem with my increment/decrement - it changes quantity of all added items, not the particular one I clicked. Also I want it to update cartTotal every time I'm clicking on incrementCount/decrementCount.
Here is the sandbox code
https://codesandbox.io/s/epic-night-s575k?file=/src/components/Menu.jsx
You need to have separate state variable count for each product thats in your cart.
You need to have your components like so
Cart component:
import React from "react";
import { FaTimes } from "react-icons/fa";
export default function Cart({
product,
removeFromCart,
count,
incrementCount,
decrementCount
}) {
const { image, title, price } = product;
return (
<>
<li className="cart-item__container">
<img src={image} alt={title} className="cart-item__image" />
<h4 className="cart-item__title">{title}</h4>
<h5 className="cart-item__price">{price}$</h5>
<button disabled={count === 1} onClick={() => decrementCount(product)}>
-
</button>
<span>{count}</span>
<button onClick={() => incrementCount(product)}>+</button>
<FaTimes
className="icon__remove"
onClick={() => removeFromCart(product)}
></FaTimes>
</li>
</>
);
}
Menu component:
import React, { useState } from "react";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import "react-tabs/style/react-tabs.css";
import { FaShoppingCart } from "react-icons/fa";
import products from "./products";
import Product from "./Product";
import Cart from "./Cart";
const Menu = () => {
const [cart, setCart] = useState([]);
const addToCart = (el) => {
const cartCopy = cart.slice();
const index = cartCopy.findIndex((product) => el.id === product.id);
if (index === -1) {
cartCopy.push({ ...el, count: 1 });
} else {
const pr = cartCopy[index];
cartCopy[index] = { ...pr, count: pr.count + 1 };
}
setCart(cartCopy);
};
const removeFromCart = (el) => {
const cartCopy = cart.filter((product) => el.id !== product.id);
setCart(cartCopy);
};
const decrementCount = (el) => {
const cartCopy = cart.slice();
const index = cartCopy.findIndex((product) => el.id === product.id);
const pr = cartCopy[index];
cartCopy[index] = { ...pr, count: pr.count - 1 };
setCart(cartCopy);
};
const getCartTotal = () => {
return cart.reduce(
(total, product) => total + product.price * product.count,
0
);
};
const getCartCount = () => {
// return cart.length;
// =======OR=========
return cart.reduce((total, product) => total + product.count, 0);
};
return (
<>
<Tabs className="tabs-wrapper" id="menu">
<TabList className="tabs">
<Tab className="tab-item">Burgers</Tab>
<Tab className="tab-item">Lunch of the day</Tab>
<Tab className="tab-item">Crepes</Tab>
</TabList>
<TabPanel>
<div className="burgers">
<ul>
{products
.filter((product) => product.category === "burger")
.map((product) => (
<Product
key={product.id}
product={product}
addToCart={addToCart}
/>
))}
</ul>
</div>
</TabPanel>
<TabPanel>
<div className="lunch">
<h4>Sweet lunch today!</h4>
<span>7$</span>
<p>
You can choose one of our 3 sweet crepes + one of our 4 cold
drinks!
<br />
Nutella crepe, Crepe with salted caramel and nuts or Oreo Bang
crepe with whipped cream and raspberries.
<br />
For drink - one of our homemade lemonades - Melon, Orange or
Lemon-Mint. Or a Frozen Coffee!
</p>
</div>
</TabPanel>
<TabPanel>
<div className="crepes">
<ul>
{products
.filter((product) => product.category === "crepe")
.map((product) => (
<Product
key={product.id}
product={product}
addToCart={addToCart}
/>
))}
</ul>
</div>
</TabPanel>
</Tabs>
<FaShoppingCart className="cart-icon">{getCartCount()}</FaShoppingCart>
{cart.map((el) => (
<Cart
key={el.id}
product={el}
count={el.count}
removeFromCart={removeFromCart}
incrementCount={addToCart}
decrementCount={decrementCount}
/>
))}
<h4 className="cart__total">Your Order Total Price: {getCartTotal()}$</h4>
<button className="btn__clear" onClick={() => setCart([])}>
Clear cart
</button>
</>
);
};
export default Menu;
I'm trying to figure out how to open one item(at my situation image gallery), but not all at once.
I'm using FsLightbox dependencies. My code seems working until I'm triggering button to open the image gallery, but it opens all three columns.
index.js
import React, { useState } from 'react';
import Menu from './Menu';
import Button from './Button';
import data from './Data';
import './style.css';
const allCategories = ['Visi', ...new Set(data.map(item => item.category))];
const Projects = () => {
const [menuItem, setMenuItem] = useState(data);
const [buttons, SetButtons] = useState(allCategories);
// console.log(data);
const filter = (button) => {
if(button === 'Visi'){
setMenuItem(data);
return;
}
const filteredData = data.filter(item => item.category === button);
setMenuItem(filteredData);
}
return (
<div id="projects" className="App">
<div className="title">
Projektai
</div>
<Button button={buttons} filter={filter}/>
<Menu menuItem={menuItem}/>
</div>
)
}
export default Projects
all my data and images in data.js file
import skardis1 from '../../images/projects/1.jpg'
import skardis2 from '../../images/projects/2.jpg'
import skardis3 from '../../images/projects/3.jpg'
const data = [
{
id:1,
image: skardis1,
title: 'PAVADINIMAS',
category: 'Privatūs',
description: 'ČIA KAŽKOKS TEKSAS',
projectimages: [skardis2, skardis3]
},
{
id:2,
image: skardis2,
title: 'PAVADINIMAS',
category: 'Visuomeniniai',
description: 'ČIA KAŽKOKS TEKSAS',
projectimages: [ skardis3, skardis2 ]
},
{
id:3,
image: skardis3,
title: 'PAVADINIMAS',
category: 'Visuomeniniai',
description: 'ČIA KAŽKOKS TEKSAS',
projectimages: [ skardis3, skardis2 ]
},
];
export default data;
and the main Menu.js file
import React, { useState } from 'react';
import FsLightbox from 'fslightbox-react';
import {Animated} from "react-animated-css";
function Menu({menuItem}) {
const [toggler, setToggler] = useState(false);
return (
<div className="item">
{
menuItem.map((item) => {
return <div className="item-con" key={item.id}>
<Animated animationIn="fadeIn" animationOut="fadeOut" animationInDuration={1800} animationOutDuration={2400} isVisible={true}>
<div className="item-container">
<img src={item.image} alt=""/>
<h2>{item.title}</h2>
<p>{item.description}</p>
<button onClick={() => setToggler(!toggler)}> Daugiau</button>
</div>
</Animated>
</div>
})
}
{
menuItem.map((item) => {
return <div>
<FsLightbox
toggler={toggler}
sources={item.projectimages}
/>
</div>})
}
</div>
)
}
export default Menu;
As I said it, then I clicked the button, it opens all three items. I want to click on the button to one item section; it opens only that item images in preview.. now I have to close the image preview three times. added IMG how my items columns look now
This is because all the FsLightbox has a shared toggler state.
To solve this, you could create a MenuItem component to render each item, each with it's own toggler state. For example:
function Menu({ menuItems }) {
return (
<div className="items">
{menuItems.map(item => <MenuItem item={item} />)}
</div>
);
}
function Menuitem({ item }) {
const [toggler, setToggler] = useState(false);
return (
<div className="item">
<div className="item-con" key={item.id}>
<Animated animationIn="fadeIn" animationOut="fadeOut" animationInDuration={1800} animationOutDuration={2400} isVisible={true}>
<div className="item-container">
<img src={item.image} alt="" />
<h2>{item.title}</h2>
<p>{item.description}</p>
<button onClick={() => setToggler(!toggler)}> Daugiau</button>
</div>
</Animated>
</div>
<div>
<FsLightbox toggler={toggler} sources={menuItem.projectimages} />
</div>
</div>
);
}
Now that each MenuItem has it's own local state, they should open individually based on the button you clicked.
Hello Stackoverflow community!
I'am practicing with react. I am building a very simple shopping cart system. With the app you can select from products. It adds to the shoppingcart. I'm got this error message: TypeError: Cannot read property 'details' of null.
I'am attaching my code.
App.js
import React, {useState, useEffect } from "react";
import Shop from "./Shop";
import Cart from "./Cart"
const ShoppingItems = [{
id: 1,
details: {
type: "cloth",
name: "Blue jacket",
price: 15000
}
},
{
id: 2,
details: {
type: "cloth",
name: "Trousers",
price: 9990
}
},
{
id: 3,
details: {
type: "cloth",
name: "T-shirt",
price: 5000
}
}
];
const App = () => {
const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => {console.log(selectedItem)}, [selectedItem]);
return(
<div className="ui container">
<Shop Shopitems={ShoppingItems} setSelectedItem={setSelectedItem}/>
<Cart selectedItem={selectedItem}/>
</div>
);
};
export default App;
Shop.js
import React from "react";
const Shop = ({Shopitems, setSelectedItem}) => {
const AddItem = (id) => {
const selectedItem = Shopitems.find( item => item.id === id);
if(selectedItem)
{
setSelectedItem(selectedItem);
}
return;
};
const renderedItems = Shopitems.map((shopitem) => {
return(
<div key={shopitem.id} className="card">
<div className="content">
<div className="header">{shopitem.details.name}</div>
<div className="description">
{shopitem.details.price + " Ft"}
</div>
</div>
<div onClick={() => AddItem(shopitem.id)} className="ui bottom attached button">
<i className="cart icon"></i>
Add to cart
</div>
</div>
);
});
return (
<div className="ui cards">{renderedItems}</div>
);
};
export default Shop;
Cart.js
import React, {useState, useEffect} from "react";
const Cart = ({selectedItem}) => {
const [shoppingCart, setShoppingCart] = useState([]);
useEffect(() => {
//Adding to the shopping cart when an element selected
setShoppingCart([...shoppingCart, selectedItem]);
}, [selectedItem]);
const renderedItems = shoppingCart.map((item) => {
return(
<ul>
<li key={item.id}>
<div className="item">
{item.details.name}
</div>
</li>
</ul>
);
});
return(
<div>
<h1>Shopping Cart</h1>
{renderedItems}
</div>
);
};
export default Cart;
You need to verify that selectItem is not null because you cannot use .map on null, it needs to be an array ([]).
change you code to
import logo from './logo.svg';
import './App.css';
import { useEffect, useState } from 'react';
import Records from './Records';
import Shop from "./Shop";
import Cart from "./Cart"
const ShoppingItems = [{
id: 1,
details: {
type: "cloth",
name: "Blue jacket",
price: 15000
}
},
{
id: 2,
details: {
type: "cloth",
name: "Trousers",
price: 9990
}
},
{
id: 3,
details: {
type: "cloth",
name: "T-shirt",
price: 5000
}
}
];
const App = () => {
const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => {console.log(selectedItem)}, [selectedItem]);
return(
<div className="ui container">
{selectedItem !== null ? <><Shop Shopitems={ShoppingItems} setSelectedItem={setSelectedItem}/> <Cart selectedItem={selectedItem}/> </>: <></>}
</div>
);
};
export default App;
This wil render without error but it is blanc page because you pass 'null' as value.
It may not be a value for the first time. To solve this problem, you can bet where you have this error:for example:
const renderedItems = Shopitems?Shopitems.map((shopitem) => {
return(
<div key={shopitem.id} className="card">
<div className="content">
<div className="header">{shopitem.details.name}</div>
<div className="description">
{shopitem.details.price + " Ft"}
</div>
</div>
<div onClick={() => AddItem(shopitem.id)} className="ui bottom attached button">
<i className="cart icon"></i>
Add to cart
</div>
</div>
);
}) :[] ;
return (
<div className="ui cards">{renderedItems}</div>
);
};
export default Shop;
'
Again the same tutorial I have a new problem in the cart I am still new to using react redux so thanks for any help for explained I am creating the list of products ordered and counted (after) to be able to modify them added or canceled
TypeError: Cannot destructure property 'cartItems' of 'cart' as it is undefined.
import React, { useEffect } from 'react';
import { addToCart } from '../actions/cartActions';
import { useDispatch, useSelector } from 'react-redux';
function CartScreen(props) {
const cart = useSelector(state => state.cart);
const { cartItems } = cart;
const productId = props.match.params.id;
const qty = props.location.search ? Number(props.location.search.split("=")[1]) : 1;
const dispatch = useDispatch();
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty));
}
}, []);
return <div className="cart">
<div className="cart-list">
<ul className="cart-list-container">
<li>
<h3>
Shopping Cart
</h3>
<div>
Price
</div>
</li>
{
cartItems.length === 0 ?
<div>
Cart is empty
</div>
:
cartItems.map(item =>
<li>
<div className="cart-image">
<img src={item.image} alt="product" />
</div>
<div className="cart-name">
<div>
Qty:
<select value={item.qty} onChange={(e) => dispatch(addToCart(item.product, e.target.value))}>
{[...Array(item.countInStock).keys()].map(x =>
<option key={x + 1} value={x + 1}>{x + 1}</option>
)}
</select>
</div>
</div>
<div className="cart-price">
${item.price}
</div>
</li>
)
}
</ul>
</div>
<div className="cart-action">
</div>
</div>
}
export default CartScreen;
A default value is needed. Somewhere at top-level modules of store:
const EMPTY_CART = { cartItems: [] }; // To ensure that default value is singleton and avoid useless re-renders
inside component:
const cart = useSelector(state => state.cart || EMPTY_CART);
const { cartItems } = cart;
It is better to define default values at reducers level.
Another approach is to pass defaultValue to createStore.
https://redux.js.org/recipes/structuring-reducers/initializing-state