I have a Menu component, which displays my Menu items. I also have here function addToCart and Cart icon. Function addToCart works fine and store array of selected items.
const Menu = () => {
const [cart, setCart] = useState([]);
const addToCart = (el) => setCart( [...cart, el]);
console.log(cart);
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>
{burgers.map(burger => (
<li key={burger.id}>
<h4>{burger.title}</h4>
<span>{burger.price}</span>
<img src={burger.image} alt={burger.title} />
<p>{burger.description}</p>
<button type="submit" onClick={() => addToCart(burger.title, "burger")}>Add to cart</button>
</li>
))}
</ul>
</div>
</TabPanel>
<TabPanel>
<div className="crepes">
<ul>
{crepes.map(crepe => (
<li key={crepe.id}>
<h4>{crepe.title}</h4>
<span>{crepe.price}</span>
<img src={crepe.image} alt={crepe.title} />
<p>{crepe.description}</p>
<button type="submit" onClick={() => addToCart(crepe.title, "crepe")}>Add to cart</button>
</li>
))}
</ul>
</div>
</TabPanel>
</Tabs>
<FontAwesomeIcon className="cart" icon={["fas", "shopping-cart"]}/>
</>
)
}
How to pass these items info to Cart component, which I want to open on Cart icon click? Also I would like to avoid router if it's possible.
Edit:
How to make Cart component show image,title and price properties of items, added to a Cart?
const Cart = (props) => {
return (
<>
<div>
<img src={props.image} alt={props.title} />
<h4>{props.title}</h4>
<span>{props.price}</span>
</div>
<Form/>
</>
)
}
You can pass cart with other props to CartComponent.
<CartComponent cart={ cart }/>
If your CartCompoennt is outside menu you should bring your cart state up in that way to by able pass them where you need.
function App() {
const [cart, setCart] = useState([]);
const addToCart = (el) => setCart( [...cart, el]);
return (
<div>
<CartComponent cart={ cart }/>
<Menu cart={ cart } addToCart={ addToCart } />
</div>
)
}
Related
I have a nested list with some items
Currently I'm displaying an arrow for each item, which results in a problem - if item has no sub-items, it still shows an arrow.
ListItem code (classNames are excluded for a better reading):
function CodeItem({ props }: CodeItemProps) {
const cpvs = Object.keys(tree) as Array<CpvLabelKey>;
const [open, setOpen] = React.useState(cpvs.some(c => codes.includes(c)));
return (
<div>
<fieldset>
<div >
<button
onClick={() => setOpen(!open)}
aria-expanded={open}
>
// this icon should be displayed only if Item has sub-elements
<UilAngleRight/>
</button>
<input
type="checkbox"
name={`cpv-${code}`}
id={`cpv-${code}`}
checked={checked}
onChange={e => onChange(e.target.checked, code)}
/>
</div>
<label htmlFor={`cpv-${code}`}>
{code} - {tCPV(code)}
</label>
</fieldset>
// where I map each Item
{open && (
<div>
{cpvs.map(code => (
<CodeItem
key={code}
code={code}
onChange={onChange}
tree={tree[code]?.children || {}}
checked={codes.includes(code)}
codes={codes}
/>
))}
</div>
)}
</div>
);
}
How could I hide arrow on last elements, which has no over sub-elements?
You can check if the array is not empty before rendering the icon
{!!cpvs.length && <UilAngleRight/>}
I have Menu component. I'm adding items to Cart component by clicking on Add to Cart button.
I also have a Cart icon which holds array of items in Cart.
const Menu = () => {
const [cart, setCart] = useState([]);
const addToCart = (el) => setCart( [...cart, el]);
console.log(cart);
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>
{burgers.map(burger => (
<li key={burger.id}>
<h4>{burger.title}</h4>
<span>{burger.price}</span>
<img src={burger.image} alt={burger.title} />
<p>{burger.description}</p>
<button type="submit" onClick={() => addToCart(burger, "burger")}>Add to cart</button>
</li>
))}
</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>
<img src={sweetLunch} alt="Sweet crepe lunch" />
<img src={sweetCrepes} alt="Sweet crepes lunch" />
</div>
</div>
</TabPanel>
<TabPanel>
<div className="crepes">
<ul>
{crepes.map(crepe => (
<li key={crepe.id}>
<h4>{crepe.title}</h4>
<span>{crepe.price}</span>
<img src={crepe.image} alt={crepe.title} />
<p>{crepe.description}</p>
<button type="submit" onClick={() => addToCart(crepe, "crepe")}>Add to cart</button>
</li>
))}
</ul>
</div>
</TabPanel>
</Tabs>
<FontAwesomeIcon className="cart" icon={["fas", "shopping-cart"]} onClick={() => setCart(cart)}/>
</>
)
}
I want to make Cart component appear above Menu component, display on the right side and take a half of screen(like on Upwork when you click on jod in feed) when I click on Cart icon.
So I tried to import Cart component (which also holds a Form) into Menu component
const Cart = ({ cart }) => {
const { image, title, price } = cart;
return (
<>
<li>
<img src={image} alt={title} />
<h4>{title}</h4>
<span>{price}</span>
</li>
<Form />
</>
)
}
and got
TypeError: Cannot destructure property 'image' of 'cart' as it is undefined.
at Cart (Cart.jsx:6)
My App.jsx looks like this
import React, { useState } from "react";
import {
BrowserRouter as Router,
Route,
Switch,
Redirect
} from "react-router-dom";
import './App.css';
import './Responsive.css';
const Header = React.lazy(() => import("./components/Header"));
const Footer = React.lazy(() => import("./components/Footer"));
const Home = React.lazy(() => import("./components/Home"));
const Menu = React.lazy(() => import("./components/Menu"));
function App() {
const [cart, setCart] = useState([]);
return (
<Router>
<React.Suspense fallback={<p className="loader">Loading...</p>}>
<Header />
<Switch>
<Route path="/home" render={props => <Home {...props} />} />
<Route path="/menu" render={props => <Menu cart={cart} {...props} />} />
<Route exact path="/">
<Redirect to="/home" />
</Route>
</Switch>
<Footer/>
</React.Suspense>
</Router>
);
}
export default App;
props which you try to pass is an empty table
const [cart, setCart] = useState([]);
<Menu cart={cart} />
After some restructuring I managed to find an answer for displaying cart items
{cart.map((el) => (
<Cart
key={el.id}
product={el}
removeFromCart={removeFromCart}
/>
))}
And Cart component itself
export default function Cart ({ product, removeFromCart }) {
const { image, title, price } = product;
In my react project, my purpose is removing item when I click the trash icon. I tried to reach li elements using parentNode or parentElements but when I do this whenever I click the trash button console shows me different parent elements. Sometimes shows me li but sometimes icons div. I couldn't understand thats why
output is like that (img)
const removeItem = (item) => {
const liITem = item.target.parentNode.parentNode.parentNode;
console.log(liITem);
//liITem.remove();
};
return (
<Container>
<ToDoForm onSubmit={addTodo} />
<ul className="items">
{todos.map((todo) => (
<li className="item" key={todo.id}>
{todo.text}
<div className="icons">
<button className="icon">
<AiFillEdit />
</button>
<button className="icon">
<BsFillTrashFill onClick={removeItem} />
</button>
</div>
</li>
))}
</ul>
</Container>
);
You can pass the id of the item you want to remove.
const removeItem = (id) => {
const todosUpdated = todos.filter((elt) => elt.id != id )
setTodos(todosUpdated )
};
return (
<Container>
<ToDoForm onSubmit={addTodo} />
<ul className="items">
{todos.map((todo) => (
<li className="item" key={todo.id}>
{todo.text}
<div className="icons">
<button className="icon">
<AiFillEdit />
</button>
<button className="icon">
<BsFillTrashFill onClick={() => removeItem(todo.id)} />
</button>
</div>
</li>
))}
</ul>
</Container>
);
In this e-commerce project, onClick "add to cart" should increment cart count, and change button textContent to "Remove from cart", a second onClick of the same element should decrement count and change textContent back to "Add to cart". I have shown 2 different conditional syntaxes that produce opposite behaviours.
function RenderBooks({book, updateCart}) {
return(
<Card className="allbooks">
<CardBody>
<Link to="/" style={{textDecoration:'none', color:'black'}} >
<CardTitle><strong>{book.name}</strong></CardTitle>
<CardImg width="100%" src={bookImg} alt="book image" />
<CardText>{book.price}</CardText>
</Link>
<form>
<Button className="add-to-cart" style={{textDecoration:'none'}} color="link"
onClick={(e, id) => updateCart(e, book.id)}
>
Add to cart
</Button>
</form>
</CardBody>
</Card>
);
}
In the first condition of updateCart, event.target.textContent fires, but this.props.addToCart, which calls redux dispatch in the parent component, won't fire, while the reverse is the case in the else condition, that is, the dispatch function fires, and event.target doesn't. How may i get the dispatch function to fire after event.target.textContent fires, thanks in advance.
class Books extends React.Component {
constructor(props){
super(props);
this.updateCart = this.updateCart.bind(this);
}
updateCart(event, id) {
if (event.target.textContent === 'Add to cart') {
event.target.textContent = 'Remove from cart';
const count = this.props.cartcount.cartcount;
() => {
this.props.addToCart(id, count);
}
}
else {
event.target.textContent = 'Add to cart';
let count = this.props.cartcount.cartcount;
this.props.subtractFromCart(id, count);
}
}
render() {
const count = this.props.cartcount.cartcount;
const book = this.props.books.books.map((book, index) => {
return (
<div key={index} className="col-8 col-md-4 col-lg-3">
<RenderBooks book={book} updateCart={this.updateCart} />
</div>
);
});
return (
<div>
<Navbar dark expand="md" id="nav-books">
<div className="container">
<Nav className="mr-auto" navbar>
<NavItem>
<NavLink className="nav-link" to='/'>
<BsArrowLeft />
</NavLink>
</NavItem>
</Nav>
<NavbarBrand className="mc-auto logo" href="/">Soteria</NavbarBrand>
<Nav className="ml-auto" navbar>
<NavItem>
<NavLink className="nav-link" to='/'>
<FiShoppingCart /> {count}
</NavLink>
</NavItem>
</Nav>
</div>
</Navbar>
<div className="container books">
<div className="row">
{book}
</div>
</div>
</div>
);
}
}
export default Books;
I learned that redux-thunk is synchronous, so the event listener is lost by the time the store is updated. So i eventually thought up what is an intuitive react-redux solution by adding itemids param, an array that stores clicked items ids, and updating the UI in RenderBooks() with ternary operators;
function RenderBooks({book, itemids, updateCart}) {
return(
<Card>
<CardBody>
<Link to="/" style={{textDecoration:'none', color:'black'}} >
<CardTitle><strong>{book.name}</strong></CardTitle>
<CardImg width="100%" src={bookImg} alt="book image" />
<CardText>{book.price}</CardText>
</Link>
{
itemids.length != 0 ?
itemids.includes(book.id) ?
<Button onClick={(e, id) => updateCart(e, book.id)}>
Remove from cart
</Button>
:
<Button onClick={(e, id) => updateCart(e, book.id)}>
Add to cart
</Button>
:
<Button onClick={(e, id) => updateCart(e, book.id)}>
Add to cart
</Button>
}
</CardBody>
</Card>
);
}
So in updateCart, i only use event.target.textContent to determine which props.function to call.
class Books extends React.Component {
constructor(props){
super(props);
this.updateCart = this.updateCart.bind(this);
}
updateCart(event, id) {
let target = event.target.textContent;
if (target === 'Add to cart') {
this.props.addToCart(id)
}
else {
this.props.subtractFromCart(id);
}
}
render() {
const book = this.props.books.books.map((book, index) => {
return (
<div key={index} className="col-8 col-md-4 col-lg-3">
<RenderBooks book={book} itemids={this.props.itemids}
updateCart={this.updateCart}
/>
</div>
);
});
return (
<div>
<div className="container books">
<div className="row">
{book}
</div>
</div>
</div>
);
}
}
export default Books;
I have this class in React:
render() {
let addedItems = this.props.items.length ? (
this.props.items.map(item => {
return (
<li className="collection-item avatar" key={item.id}>
<div className="item-desc">
<Modal trigger={<Button onClick={this.handleOpen}>Editar</Button>}>
<Header icon="archive" content="Archive Old Messages" />
<Modal.Content>
{/* CHEESE */}
<Button.Group>
<Link to="/cart">
<Button
icon="plus"
onClick={() => {
console.log("BUT +");
this.handleCheese(item, "+");
}}
/>
</Link>
<Button content="Cheese" labelPosition="left" />
<Link to="/cart">
<Button
icon="minus"
onClick={() => {
this.handleCheese(item, "-");
}}
/>
</Link>
<h2>{item.queijo}</h2>
</Button.Group>
</Modal.Content>
</Modal>
</div>
</li>
);
})
)
}
In resume a modal should open according to the object I'm selecting.
But in my code the item.id is selecting the last object I inserted in the addedItems.
I need the modal to have the info about the obj I selected.
In case you want to see all code is in: https://github.com/fernanda-avelar/burguer_cart ->
This page is the /src/components/Cart.js
I guess you can have only one modal in your window, that's why it's taking the last one (by having overriden all others).
So instead you should put your modal out of the .map.
Also, keep track of the selected item via a controlled state selectedItem.
Then use it the the Modal.Content :
render() {
return (
<>
<Modal.Content>
/* content depending of this.state.selectedItem */
</Model.Content>
/* your other stuff */
</>
)
}