I have parent component, name ShoppingCartDetail. It will render many child component, name ShoppingCartProduct, each child component shows quantity of product, it has buttons to change quantity, and a button to remove current product from array of products that is added to shopping cart (like shopping cart on shopping websites).
My problem is: if change quantity of a product the press remove. The state of the removed component will override state of the next component. If I don't increase/ decrease, no problem happen.
I have already add key attribute to child component but it not help. I would very grateful if you can give any advice.
1. ShoppingCartDetail.js (parent):
import React from 'react';
import {NavLink} from "react-router-dom";
import {FontAwesomeIcon} from "#fortawesome/react-fontawesome";
import {faReplyAll, faLock} from "#fortawesome/free-solid-svg-icons";
import {connect} from "react-redux";
import ShoppingCartProduct from './ShoppingCartProduct';
const ShoppingCartDetail = (props) => {
const {Cart, Products, Currency} = props.Data;
// format thounds seperator
function formatNumber(num) {
return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')
}
/* Remove duplicate product in Cart arr based on ID*/
const currentCart = [...Cart];
// get ID list of current Cart
let currentIdList = [];
currentCart.forEach((product) => {
currentIdList.push(product.id);
})
// removed duplicated ID
let removedDuplicateIdList = [];
currentIdList.forEach((id) => {
if (!removedDuplicateIdList.includes(id)){
removedDuplicateIdList.push(id)
}
})
// create product array base on ID list, use this array to render shopping cart
let splicedProductsList = [];
removedDuplicateIdList.forEach((id) => {
Products.forEach((product)=>{
if (product.id === id){
splicedProductsList.push(product)
}
})
})
/* End remove duplicate product in Cart arr based on ID*/
// Calculate total amount of Shopping Cart (re-use currentIdList)*/
var totalAmount = 0;
currentIdList.forEach((id) => {
Products.forEach((product)=>{
if (product.id === id){
totalAmount += product.price // (default is USD)
}
})
})
// fortmat money (currency and operator and decimal)
const showMoneyTotal = (currency, amount) => {
switch (currency) {
case "USD":
return "$ " + formatNumber((amount).toFixed(2))
case "EUR":
return "€ " + formatNumber((amount * 0.84).toFixed(2))
case "GBP":
return "£ " + formatNumber((amount * 0.76).toFixed(2))
default:
return "$ " + formatNumber((amount).toFixed(2))
}
}
return (
<section className="shopping-cart-detail">
<div className="container">
<div className="row title">
<div className="col">
<h4>Your Shopping Cart</h4>
</div>
<div className="col-auto">
<NavLink
to = "/all-collection"
exact = {true}
>
<FontAwesomeIcon icon = {faReplyAll} className="icon"/>
Continue Shopping
</NavLink>
</div>
</div>
<div className="row shopping-cart-content">
<div className="col-6 order">
<div className="content">
<div className="title">Order Summary</div>
<div className="info">
<div className="container">
{
splicedProductsList.map((product, index) => {
return (
<ShoppingCartProduct Product = {product} key = {product}></ShoppingCartProduct>
)
})
}
</div>
</div>
</div>
</div>
<div className="col-3 total-amount">
<div className="content">
<div className="title">Your Cart Total</div>
<div className="info">
<div className="money">
<span> {showMoneyTotal(Currency.currency, totalAmount)} </span>
</div>
<div className="check-out">
<div className="wrap">
<FontAwesomeIcon icon = {faLock} className = "icon"/>
<NavLink
to="/"
exact={true}
>
Proceed To Checkout
</NavLink>
</div>
</div>
<div className="payment-card">
<img
alt="payment-card"
src={require("../../Assets/images/section-shopping-cart-detail/payment.webp")}
/>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
const mapStateToProps = (state) => {
return {
Data: state
}
}
export default connect(mapStateToProps)(ShoppingCartDetail)
2.ShoppingCartProduct.js (child)
import React, {useState} from 'react';
import {NavLink} from "react-router-dom";
import {FontAwesomeIcon} from "#fortawesome/react-fontawesome";
import {faPen, faTimesCircle } from "#fortawesome/free-solid-svg-icons";
import {connect} from "react-redux";
import urlSlug from "url-slug";
const ShoppingCartProduct = (props) => {
const {Cart, Currency} = props.Data;
const {Product, dispatch} = props;
// count quantity of a product in Shopping Cart
const countProduct = (id) => {
let count = 0;
Cart.forEach((product) => {
if (product.id === id) {
count += 1;
}
})
return count;
}
const [state, setState] = useState({
update_quantity: countProduct(Product.id),
name: Product.name
})
// increase quantity when click plus button
const increaseAddedAmount = () => {
setState((prevState) => {
return {
...prevState,
update_quantity: prevState.update_quantity + 1
}
})
}
// decrease quantity when click substract button
const decreaseAddedAmount = () => {
if (state.update_quantity > 0) {
setState((prevState) => {
return {
...prevState,
update_quantity: prevState.update_quantity - 1
}
})
}
}
return (
<div className={`row product-row ${Product.name}`} key = {Product}>
<div className="col-2 img">
<img
alt="product"
src = {Product.main__image}
/>
</div>
<div className="col-10 product-detail">
<div className="row name">
<div className="col">
<NavLink
to = {`/product/${urlSlug(Product.name)}`}
exact = {true}
>
{Product.name}
</NavLink>
</div>
</div>
<div className="row price">
<div className="col">{showMoney(Currency.currency, 1, Product)}</div>
</div>
<div className="row quantity-title">
<div className="col">
Quantity
</div>
</div>
<div className="row quantity-row">
<div className="col-3 quantity">
<div className="wrap">
<span
className="decrease"
onClick = {()=>{decreaseAddedAmount()}}
>-</span>
<span className='count'>
{state.update_quantity}
</span>
<span
className="increase"
onClick = {()=>{increaseAddedAmount()}}
>+</span>
</div>
</div>
<div className="col-3 update-quantity">
<div
className="wrap"
onClick = {()=>dispatch({type: "UPDATE", id: Product.id, quantity: state.update_quantity})}
>
<FontAwesomeIcon icon = {faPen} className="icon"/>
Update
</div>
</div>
<div className="col-3 remove-product">
<div
className="wrap"
onClick = {()=>{dispatch({type: "REMOVE", id: Product.id})}}
>
<FontAwesomeIcon icon = {faTimesCircle} className="icon"/>
Remove
</div>
</div>
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
Data: state
}
}
export default connect(mapStateToProps)(ShoppingCartProduct)
For simplicity try to pass the clicked product as a prop and then use javascript method called findIndex() to find the id of the clicked product inside the Cart array and store it in a variable. After getting the id then use splice to generate a new array of cart without that id that you found. You can use this snippet of code for reference.
const index = this.state.cart.findIndex(
(cartItem) => cartItem.id === clickedProduct.id
);
let newCart = [...this.state.cart];
if (index >= 0) {
newCart.splice(index, 1);
}
Related
I tell him I am transferring an event from the component
child ( ItemCount) to the parent component parent ItemDetail the onADD event that only acts if an item is passed to it and when it does, the state becomes true.
The child has an event called add to cart which triggers the event and passes a product counter.
It runs perfectly but it throws me a warning that is the following.
react-dom.development.js:86 Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
Can you tell me the mistake and what I did wrong? from now very grateful
I share the codes Thanks
ItemCount (component child)
import React, { useState, useContext} from 'react';
import 'materialize-css/dist/css/materialize.css';
import '../App.css';
import {FontAwesomeIcon} from '#fortawesome/react-fontawesome';
import {faPlus, faMinus, faPowerOff} from '#fortawesome/free-solid-svg-icons';
import {contextoProducto} from './ProductContext';
import swal from 'sweetalert';
const ItemCount = ({item, stockInitial, initial = 0, onAdd}) => {
const [contador, setContador] = useState(initial)
const [stock, setStock] = useState(stockInitial)
const { addProduct } = useContext(contextoProducto);
const sumar = () => {
setContador(contador + 1)
setStock(stock - 1);
avisarStock();
}
const restar= () => {
if(contador > 0){
setContador(contador - 1);
setStock(stock + 1);
}
else
{
setContador(0);
}
}
const reset = () =>{
setContador(0);
setStock(stockInitial);
}
const avisarStock = () => {
if(stock > 0 ){
}
else{
swal('No podemos enviar su envio no hay stock', "Gracias", "error");
setStock(0);
setContador(contador)
}
}
const agregarAlCarrito = () => {
onAdd(contador);
}
return(
<div>
<div className=" row left text">Stock: {stock}</div>
<article>{contador}</article>
<div className="buttonCount">
<button onClick={sumar}>
<FontAwesomeIcon icon ={faPlus}/>
</button>
<button onClick={restar}>
<FontAwesomeIcon icon={faMinus}/>
</button>
<button onClick={reset}>
<FontAwesomeIcon icon={faPowerOff}/>
</button>
<br/><h2>{avisarStock}</h2>
<button onClick={() => addProduct({...item, quantity: contador}) || agregarAlCarrito()} > Agregar al carrito </button>
</div>
</div>
)
}
export default ItemCount;
ItemDetail (component father)
import React, { useState } from "react";
import '../App.css';
import 'materialize-css/dist/css/materialize.css';
import Count from './ItemCount';
import { Link } from "react-router-dom";
export const ItemDetail = ({item}) => {
const [itemSell, setItemSell] = useState(false);
const onAdd = (count) => {
setItemSell(true);
}
return (
<main className="itemsdetails">
<div className="row" id= {item.id}>
<div className="col s12 m6">
<img src={item.image} alt="item" className="itemImg responsive-img"/>
</div>
<div className="col s12 m6">
<div className="col s12">
<h5 className="itemName">{item.title}</h5>
</div>
<div className="col s12">
<p className="itemDescription">{item.description}</p>
</div>
<div className="col s12">
<p className="itemPrice"> {item.price}</p>
</div>
<div className="col s12">
{
itemSell ? <Link to="/cart"><button className="waves-effect waves-light btn-large">Finalizar Compra</button></Link> : <Count item= {item} stockInitial={item.stock} onAdd= { onAdd } />
}
</div>
</div>
</div>
</main>
)
};
export default ItemDetail;
<br/><h2>{avisarStock}</h2>
Here, you are trying to render a component, but actually avisarStock is a function which sets state and opens an alert. It makes no sense to try to render this function.
It would appear you meant to render stock not avisarStock. This would show your stock state in the <h2>:
<br/><h2>{stock}</h2>
<br/><h2>{avisarStock}</h2> avisarStock is a function, and since Component can be function, react thinks you are doing Component instead of <Component />
I have a basic app which simply returns a number of cards with some content inside, I have some buttons which then filter the returned cards by a value found in the dataset. The filter buttons do work indididually but if I click one after the other the filter is being applied to the now filtered data. How can I make sure the filter is being applied to the initial state of the data or how can I reset the state to everything before the filter is applied? Thanks.
parent.js
import './App.scss';
import DataThoughts from "./assets/data/DataThoughts";
import Thoughts from "./components/Thoughts";
function AppThoughts() {
return (
<div className="App">
<main className={'bg-light'}>
<div className={'container'}>
<Thoughts data={DataThoughts} />
</div>
</main>
</div>
);
}
export default AppThoughts;
Thoughts.js
import React, { Component } from 'react';
import FilterButton from "./FilterButton";
class Thoughts extends Component {
constructor(props) {
super(props);
this.state = {...props};
}
handleClick = value => () => {
let filtered = this.state.data.filter(item => item.Emotion === value);
this.setState({ data: filtered });
console.log(this.state.data);
};
render() {
let data = this.state.data;
let numberOfThoughts = data.length;
let dataList = this.state.data.map((thought, i) =>
<div className={`col-12 col-sm-12 col-md-6 ${i % 2 === 0 ? i % 3 === 0 ? 'col-lg-3 col-xl-3' : 'col-lg-5 col-xl-5' : 'col-lg-4 col-xl-4'}`} key={'thought'+i}>
<div className="card mb-4">
{thought.Photo ? <img src={thought.Photo} className="card-img-top" alt={thought.Emotion}/> : ''}
<div className="p-5">
<blockquote className="blockquote mb-0">
<p className={'small text-muted'}>{thought.Date}</p>
<p className={`${i % 2 === 0 ? i % 3 === 0 ? 'display-6' : 'display-4' : 'display-5'} mb-4`}>{thought.Thought}</p>
<footer className="small text-muted">{thought.Author}, <cite title="Source Title">{thought.Location}</cite></footer>
</blockquote>
</div>
</div>
</div>
);
return (
<section className="row section-row justify-content-start thoughts">
<div className={`col-12`} key={'filters'}>
<FilterButton buttonText={"Happy"} onClick={this.handleClick('Happy')} />
<FilterButton buttonText={"Sad"} onClick={this.handleClick('Sad')} />
<FilterButton buttonText={"Thinking"} onClick={this.handleClick('Thinking')} />
<FilterButton buttonText={"All"} onClick={this.handleClick('All')} />
</div>
{dataList}
<div className={`col-12 col-sm-12 col-md-6 col-lg-2 col-xl-2 d-flex align-items-center justify-content-center`} key={'total'}>
<span className={'display-1'}>{numberOfThoughts}</span>
</div>
</section>
);
}
}
Thoughts.defaultProps = {
Photo: '',
Emotion:'Happy',
Date:'Morning',
Thought:'Default',
Author:'Me',
Location:'Somewhere'
};
export default Thoughts; // Don’t forget to use export default!
FilterButtons.js
import React, { Component } from 'react';
class FilterButton extends Component {
render() {
return (
<button className={'btn btn-primary d-inline-block mb-4'} onClick={this.props.onClick}>{this.props.buttonText}</button>
);
}
}
export default FilterButton; // Don’t forget to use export default!
It looks like the initial data comes from the props. You can access the props with this.props. So you can do something like this:
handleClick = value => () => {
// filter the initial data
let filtered = this.props.data.filter(item => item.Emotion === value);
this.setState({ data: filtered });
// set to initial data
// this.setState({ ...this.props });
console.log(this.state.data);
};
I am new to react-hooks. I have successfully been able to load all objects coming from the api. But when I try to load a single post, it renders the object with id=1 only. I have 3 data in the backend database.
I am using Axios inside useEffect function.
My foodpage:
function Food() {
const [food, setFood] = useState([])
const [id,setId] = useState(1)
useEffect(() => {
axios.get(`https://texas-crm1.herokuapp.com/api/menus/${id}`)
.then(abc=>{
console.log(abc.data)
// console.log(abc.data.id);
setFood(abc.data)
})
.catch(err =>{
console.log(err)
})
}, [])
return (
<div>
<div className="food-page">
<PageHeader {...food} key={food.id} />;
<Customize />
<FoodDescription {...food} key={food.id} />;
</div>
</div>
);
}
export default Food;
Here, initially, i have put the state as id =1. And I am stuck how to change the state. Thats why on every food description only id=1 is rendered.
My food description:
function FoodDescription(props) {
const [quantity,setQuantity] = useState(0)
// let quantity = 0;
const handleDecrement = () => {
if (quantity > 0){
setQuantity((prev) => prev - 1);
}
else {
setQuantity(0);
}
};
const handleIncrement = () => {
setQuantity((prev) => prev + 1);
};
console.log(props);
const {food_name,long_title,subtitle,description,price,id} = props;
return (
<div className="food-description">
<div className="container">
<div className="title">
<div className="main-title">{food_name}</div>
</div>
<div className="description">
{/* {description.map((des: string, index: number) => { */}
{/* {description.map((des, index) => {
return <p key={index}>{des}</p>;
})} */}
{description}
{/* <div dangerouslySetInnerHTML="description">{description}</div> */}
</div>
<div className="order">
<div className="quantity">
{/* <div className="negative" onClick={() => this.handleDecrement()}>
-
</div>
{this.state.quantity}
<div className="positive" onClick={() => this.handleIncrement()}>
+
</div> */}
<div className="negative" onClick={handleDecrement}>-</div>
{quantity}
<div className="negative" onClick={handleIncrement}>
+
</div>
</div>
<ExploreButton active="link-active">
Add to Order - ${price}
</ExploreButton>
</div>
</div>
</div>
)
}
export default FoodDescription;
Here are three different foods. But when I click on anyone of them, only id=1 is rendered in Fooddescription page.
Update:
Components that contains the click button on Buy Now.
My MenuComponent.txs
const MenuComponent = (props: any) => {
console.log(props);
const {id,category, image,price,rating,food_name,description} = props;
// const starterMenu = starter.map
const starterMenu = [
{
id :id,
thumbnail: image,
title: category,
rating: rating,
description:description,
price: price,
},
{
id :id,
thumbnail: image,
title: category,
rating: rating,
description:description,
price: price,
},
{
id :id,
thumbnail: image,
title: category,
rating: rating,
description:description,
price: price,
},
{
id :id,
thumbnail: image,
title: category,
rating: rating,
description:description,
price: price,
},
];
const renderMenuList = () => {
switch (props.category) {
case "starters":
return <Starters />;
case "main courses":
return <MainCourses />;
case "soups & salads":
return <SoupsSalads />;
case "sliders":
return <Sliders />;
default:
break;
}
};
return (
<div className="menu-component">
<div className="title">
<div className="main-title">{props.category}</div>
</div>
<div className="menu-cards">
{starterMenu.map((starterItem, index) => {
return <MenuCard {...starterItem} key={index} />;
})}
</div>
{renderMenuList()}
</div>
);
};
export default MenuComponent;
The props are passed to MenuCard. My MenuCard is as follows:
const MenuCard = (props: any) => {
console.log(props);
return (
// Menu card
<div className="menu-card">
<div className="container">
{/* Thumbnail */}
<div className="thumbnail">
<img src={props.thumbnail} alt={props.title} />
</div>
{/* Title */}
<div className="title">{props.title}</div>
{/* Star rating */}
<div className="rating">
<StarRating rating={props.rating} />
</div>
{/* description */}
<div className="description">{props.description}</div>
<div className="bottom">
{/* price */}
<div className="price">{props.price}</div>
<Link to={`/menu/${props.id}`}>
<MenuButton highlighted="highlighted">Buy Now</MenuButton>
</Link>
</div>
</div>
</div>
);
};
export default MenuCard;
Update:2
When i go to menu it shows all the food items, And when I click on one of the Buy, now it shows correct id in the url. But still id=1 food details are shown.
I assume when you click Buy Now you hit the route /menu/:someId which renders the <Food />.
Since the selected menu's id is in the URL we can make use of the useParams hook to get the id and fire the API call. Your current code doesn't work because irrespective of the Menu Card you clicked you will always fire the API call for the id 1 . As you have hardcoded it . So to make it dynamic you can do this
import { useParams } from 'react-router-dom';
function Food() {
const [food, setFood] = useState([]);
// if your route is /menu/:menuId
const { menuId } = useParams();
useEffect(() => {
axios
.get(`https://texas-crm1.herokuapp.com/api/menus/${menuId}`)
.then((abc) => {
console.log(abc.data);
// console.log(abc.data.id);
setFood(abc.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<div>
<div className="food-page">
<PageHeader {...food} key={food.id} />;
<Customize />
<FoodDescription {...food} key={food.id} />;
</div>
</div>
);
}
Reference
useParams Hook
When I reload the page, I get this error:Cannot read property 'ArtisanID' of undefined.
How to fix this ?
I am new to this framework and thought it might be because the component instructions are not rendered when refreshing the page.
I'm using 3 context so I can't use React.Component classes unless I combine all 3 but I don't think that's a good idea.
import React, { useContext } from 'react'
import { Link } from 'react-router-dom'
import { ProductContext } from '../../global/ProductContext'
import { ArtisanContext } from '../../global/ArtisanContext'
import ProductCard from './ProductCard'
import { CartContext } from '../../global/CartContext'
const ProductPage = (props) => {
//props.preventDefault();
const {products} = useContext(ProductContext)
const {artisans} = useContext(ArtisanContext)
const { dispatch } = useContext(CartContext);
let productIDFind = props.match.params.id
const product = products.find(x=> x.ProductID === productIDFind)
const artisan = artisans.find(x=> x.ArtisanName === product.ProductArtisan)
const otherProducts = []
products.forEach(product => {
if (artisan.ArtisanName === product.ProductArtisan) {
otherProducts.push(<ProductCard key={product.ProductID} product={product} />)
}
});
return (
<section className="productPage main-section">
<div className="productPage-container main-section-constainer">
<Link className="artisanLink" to={'/Artisans/' + artisan.ArtisanID}>
<h1>Par <span>{artisan.ArtisanName}</span></h1>
</Link>
<div className="row">
<div className="productPage-principaleImg bg-img" style={{ backgroundImage: `url(${product.ProductProfilePicture})` }}></div>
<div className="productPage-info col">
<div className="row">
<div className="col">
<h2 className="productPage-Name">{product.ProductName}</h2>
<p className="productPage-Description">{product.ProductDescription}</p>
</div>
<div className="row">
<div className="productPage-Price">{product.ProductPrice}€</div>
<button className="addBtn" onClick={(e) => {
e.preventDefault();
dispatch({ type: 'ADD_TO_CART', id: product.ProductID, product })}}>
<span>Ajouter</span>
<span className="addImg bg-img"></span>
</button>
</div>
</div>
<div className="productPage-moreImg row">
<a className="productPage-YT-link" rel="noreferrer" href={product.ProductYT} target="_blank">
<div className="productPage-YT bg-img" style={{ backgroundImage: `url(${product.ProductProfilePicture})` }}></div>
<div className="productPage-YT-img bg img"></div>
</a>
<div className="productPage-img bg-img" style={{ backgroundImage: `url(${product.ProductSecondImg})` }}></div>
<div className="productPage-img bg-img" style={{ backgroundImage: `url(${product.ProductTreeImg})` }}></div>
</div>
</div>
</div>
<h3>Autre produits de {artisan.ArtisanName}</h3>
<div className="otherProducts">
{otherProducts}
</div>
</div>
</section>
)
}
export default ProductPage
ArtisanContext
import React, { createContext } from 'react'
import { db } from '../data/firebase'
export const ArtisanContext = createContext();
export class ArtisanContextProvider extends React.Component{
// Définition du state initial avec un tableau vide
state = {
artisans:[]
}
componentDidMount(){
const prevArtisans = this.state.artisans;
db.collection('Artisans').onSnapshot(snapshot => {
let changes = snapshot.docChanges();
changes.forEach(change => {
if (change.type === 'added') {
prevArtisans.push({
ArtisanID: change.doc.id,
ArtisanName: change.doc.data().ArtisanName,
ArtisanNbrProduits: change.doc.data().ArtisanNbrProduits,
ArtisanCategorie: change.doc.data().ArtisanCategorie,
ArtisanDescription: change.doc.data().ArtisanDescription,
ArtisanProfilePicture: change.doc.data().ArtisanProfilePicture,
ArtisanBanner: change.doc.data().ArtisanBanner
})
}
this.setState({
artisans: prevArtisans
})
})
})
}
render() {
return (
<ArtisanContext.Provider value={{artisans:[...this.state.artisans]}} >
{this.props.children}
</ArtisanContext.Provider>
)
}
}
I saw artisan was decalre by artisans.find. The result of artisans.find can be undefined if no item was found. So this issue will occur. To avoid this issue, you can use optional chaining:
<Link className="artisanLink" to={'/Artisans/' + artisan?.ArtisanID}>
<h1>Par <span>{artisan?.ArtisanName}</span></h1>
I am currently making a project over the database I created using Mock API. I created a button, created addToFavorites function. When the button was clicked, I wanted the selected product's information to go to the favorites, but I couldn't. I would be glad if you could help me on how to do this.
(Favorites.js empty now. I got angry and deleted all the codes because I couldn't.)
(
Recipes.js
import React, { useState, useEffect } from "react"
import axios from "axios"
import "./_recipe.scss"
import Card from "../Card"
function Recipes() {
const [recipes, setRecipes] = useState([])
const [favorites, setFavorites] = useState([])
useEffect(() => {
axios
.get("https://5fccb170603c0c0016487102.mockapi.io/api/recipes")
.then((res) => {
setRecipes(res.data)
})
.catch((err) => {
console.log(err)
})
}, [])
const addToFavorites = (recipes) => {
setFavorites([...favorites, recipes])
console.log("its work?")
}
return (
<div className="recipe">
<Card recipes={recipes} addToFavorites={addToFavorites} />
</div>
)
}
export default Recipes
Card.js
import React, { useState } from "react"
import { Link } from "react-router-dom"
import { BsClock, BsBook, BsPerson } from "react-icons/bs"
function Card({ recipes, addToFavorites }) {
const [searchTerm, setSearchTerm] = useState("")
return (
<>
<div className="recipe__search">
<input
type="text"
onChange={(event) => {
setSearchTerm(event.target.value)
}}
/>
</div>
<div className="recipe__list">
{recipes
.filter((recipes) => {
if (searchTerm === "") {
return recipes
} else if (
recipes.title.toLowerCase().includes(searchTerm.toLowerCase())
) {
return recipes
}
})
.map((recipe) => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={350} height={230} />
<h1 className="recipe__card__title">{recipe.title}</h1>
<h3 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h3>
<h3 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h3>
<button type="button" className="recipe__card__cta">
<Link
to={{
pathname: `/recipes/${recipe.id}`,
state: { recipe }
}}
>
View Recipes
</Link>
</button>
<button onClick={() => addToFavorites(recipes)}>
Add to favorites
</button>
</div>
)
})}
</div>
</>
)
}
export default Card
Final Output:
I have implemented the addToFavorite() and removeFavorite() functionality, you can reuse it the way you want.
I have to do bit of modification to the code to demonstrate its working, but underlying functionality of addToFavorite() and removeFavotie() works exactly the way it should:
Here is the Card.js where these both functions are implemented:
import React, { useState } from "react";
import { BsClock, BsBook, BsPerson } from "react-icons/bs";
function Card({ recipes }) {
const [searchTerm, setSearchTerm] = useState("");
const [favorite, setFavorite] = useState([]); // <= this state holds the id's of all favorite reciepies
// following function handles the operation of adding fav recipes's id's
const addToFavorite = id => {
if (!favorite.includes(id)) setFavorite(favorite.concat(id));
console.log(id);
};
// this one does the exact opposite, it removes the favorite recipe id's
const removeFavorite = id => {
let index = favorite.indexOf(id);
console.log(index);
let temp = [...favorite.slice(0, index), ...favorite.slice(index + 1)];
setFavorite(temp);
};
// this variable holds the list of favorite recipes, we will use it to render all the fav ecipes
let findfavorite = recipes.filter(recipe => favorite.includes(recipe.id));
// filtered list of recipes
let filtered = recipes.filter(recipe => {
if (searchTerm === "") {
return recipe;
} else if (recipe.title.toLowerCase().includes(searchTerm.toLowerCase())) {
return recipe;
}
});
return (
<div className="main">
<div className="recipe__search">
<input
type="text"
onChange={event => {
setSearchTerm(event.target.value);
}}
/>
</div>
<div className="recipe-container">
<div className="recipe__list">
<h2>all recipes</h2>
{filtered.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p>
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => addToFavorite(recipe.id)}>
add to favorite
</button>
</div>
);
})}
</div>
<div className="favorite__list">
<h2>favorite recipes</h2>
{findfavorite.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => removeFavorite(recipe.id)}>
remove favorite
</button>
</div>
);
})}
</div>
</div>
</div>
);
}
export default Card;
Here is the live working app : stackblitz
You can get the previous favourites recipes and add the new ones.
const addToFavorites = (recipes) => {
setFavorites(prevFavourites => [...prevFavourites, recipes])
console.log("its work?")
}