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?")
}
Related
I just want to show toggled item. But all map items showing up. Basically this is the result I'm getting from onclick. I think i need to give index or id to each item but i don't know how to do it. i gave id to each question didn't work.
App.js.
import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";
function App() {
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(false);
useEffect(() => {
console.log(db);
}, []);
return (
<>
<div className="container">
<div className="container-md">
<div className="faq">
<img src={bg} className="bg" />
<img src={bg1} className="bg1" />
<img src={bg2} className="bg2" />
<div className="card">
<h1>FAQ</h1>
<div className="info">
{db.map((dat) => (
<Faq
toggle={toggle}
setToggle={setToggle}
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default App;
(map coming from simple data.js file that I created. it includes just id title desc.)
Faq.js
import React from "react";
import arrow from "./images/icon-arrow-down.svg";
const Faq = ({ toggle, setToggle, title, desc, id }) => {
return (
<>
{" "}
<div className="question" onClick={() => setToggle(!toggle)}>
<p>{title}</p>
<img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
</div>
<p className="answer border">{toggle ? <>{desc}</> : ""}</p>
</>
);
};
export default Faq;
You need to store the index value of the toggle item.
You can modify the code with only 2 lines with the existing codebase.
import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";
function App() {
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(-1); //Modify Here
useEffect(() => {
console.log(db);
}, []);
return (
<>
<div className="container">
<div className="container-md">
<div className="faq">
<img src={bg} className="bg" />
<img src={bg1} className="bg1" />
<img src={bg2} className="bg2" />
<div className="card">
<h1>FAQ</h1>
<div className="info">
{db.map((dat, index) => ( //Modify Here
<Faq
toggle={index === toggle} //Modify Here
setToggle={() => setToggle(index)} //Modify Here
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default App;
import React from "react";
import arrow from "./images/icon-arrow-down.svg";
const Faq = ({ toggle, setToggle, title, desc, id }) => {
return (
<>
{" "}
<div className="question" onClick={setToggle}>
<p>{title}</p>
<img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
</div>
<p className="answer border">{toggle ? <>{desc}</> : ""}</p>
</>
);
};
export default Faq;
You will need state for each toggle. Here is a minimal verifiable example. Run the code below and click ⭕️ to toggle an item open. Click ❌ to close it.
function App({ faq = [] }) {
const [toggles, setToggles] = React.useState({})
const getToggle = key =>
Boolean(toggles[key])
const setToggle = key => event =>
setToggles({...toggles, [key]: !getToggle(key) })
return faq.map((props, key) =>
<Faq key={key} {...props} open={getToggle(key)} toggle={setToggle(key)} />
)
}
function Faq({ question, answer, open, toggle }) {
return <div>
<p>
{question}
<button onClick={toggle} children={open ? "❌" : "⭕️"} />
</p>
{open && <p>{answer}</p>}
</div>
}
const faq = [
{question: "hello", answer: "world"},
{question: "eat", answer: "vegetables"}
]
ReactDOM.render(<App faq={faq} />, document.querySelector("#app"))
p { border: 1px solid gray; padding: 0.5rem; }
p ~ p { margin-top: -1rem; }
button { float: right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Instead of doing this (in App component):
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(false);
you can write an useState hook like below to combine the two hooks and assign an isOpened property for each Faq element:
const [db, setDb] = useState(data.map(value=>{return {...value, isOpened:false}}));
and then right here you can do this (as the child of <div className="info">):
{db.map((dat, index) => (
<Faq
toggle={dat.isOpened}
setToggle={() => toggleById(dat.id)}
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
Also you need to declare toggleById function in App component:
const toggleById = (id) => {
const newDb = db.map(dat=>{
if(dat.id==id){
return {...dat,isOpened:!dat.isOpened}
}
return dat;
});
setDb(newDb);
}
and since setToggle prop of Faq, calls toggleById by its defined parameter, there is no need to do this in Faq component:
<div className="question" onClick={() => setToggle(!toggle)}>
you can simply write:
<div className="question" onClick={setToggle}>
I have a problem because I try to add place._id to another component as props (line 102), but I need to do it after click on button (line 96 and function editPlace - line 37-41) to display information about place inside popup. However I get totally various id's after refreshing the page I know that it's propably connected with rendering but to be honest I don't know how to do it properly. I've tried to add EditPlaceForm to document but ref's are not my strength yet and also I don't know if it's good strategy.
Numbers of lines are placed in comments.
import React, {
Children,
useContext, useRef, useState,
} from 'react';
import { Link } from 'react-router-dom';
import { removePlace, updatePlaceStatus } from '../../actions/FetchData';
import '../../scss/base/_users-list.scss';
import { PlacesContext } from '../../contexts/PlacesContext';
import EditPlaceForm from './EditPlaceForm/EditPlaceForm';
import '../../scss/base/_places-panel.scss';
function PlacesPanel() {
const places = useContext(PlacesContext);
const popupEl = useRef(null);
const [placeToEdit, setPlaceToEdit] = useState(0);
const handleChangeStatus = (event) => {
const changedStatus = event.target.value;
const changedPlaceId = event.target.id;
updatePlaceStatus(changedPlaceId, changedStatus);
};
const removeSelectedPlace = (event) => {
const removedPlaceId = event.target.value;
const fetchMyData = async () => {
await removePlace(removedPlaceId);
};
fetchMyData();
window.location.reload(true);
};
{/* lines 37-42 here */}
const editPlace = (e, placeId) => {
setPlaceToEdit(placeId);
popupEl.current.style.display = 'block';
console.log(placeId, placeToEdit);
};
return (
<>
<div className="page-container">
<h1>Users list</h1>
{places.map((place) => (
<div className="place-list-item" key={place._id}>
<button className="remove-user-button" value={place._id} type="submit" onClick={removeSelectedPlace}>X</button>
<div>
<Link to={place._id}><img className="place-img" src={place.img} alt="place-img" width="100" height="100" /></Link>
</div>
<div className="place-name">
<h4><Link to={place._id}>{place.name}</Link></h4>
</div>
<div className="place-address">
<h5>
{place.city}
,
{' '}
{place.street}
,
{' '}
{place.houseNo}
,
{' '}
{place.postalCode}
</h5>
</div>
<div className="place-category">
<p>
Kategoria:
{place.category}
</p>
</div>
<div className="place-district">
<p>
Dzielnica:
{place.district}
</p>
</div>
<div className="place-status">
<p>
<b>
Status:
{place.statusPlace}
</b>
</p>
</div>
<p>Zmień status: </p>
<select onChange={handleChangeStatus} id={place._id}>
<option selected value=""> </option>
<option value="pending">pending</option>
<option value="added">added</option>
</select>
{/* line 96 here */}
<input className="edit-place-button" value="Edytuj" onClick={() => editPlace(this, place._id)} type="submit" />
</div>
))}
<div className="popup" ref={popupEl}>
<div className="button-popup-container">
<button type="submit" className="button-popup" onClick={() => { popupEl.current.style.display = 'none'; }}>X</button>
{/* line 102 here */}
<EditPlaceForm placeToEdit={placeToEdit} />
</div>
</div>
</div>
</>
);
}
export default PlacesPanel;
you forgot to change at here, at handleChangeStatus,
setPlaceToEdit(changedPlaceId); adding this will resolve issue,
now after changeing options it will send new values, i hope this is what you are looking for,
const handleChangeStatus = (event) => {
const changedStatus = event.target.value;
const changedPlaceId = event.target.id;
setPlaceToEdit(changedPlaceId);
updatePlaceStatus(changedPlaceId, changedStatus);
};
you can also try to re render using useEffect hook, to ensure latest data changes happened and children rendered, useEffect(()=>{},[placeToEdit, showPop ])
const [placeToEdit, setPlaceToEdit] = useState({});
const [showPop, setShowPop] = useState(false);
useEffect(()=>{},[placeToEdit, showPop ])
const editPlace = (place) => {
setPlaceToEdit(place);
setShowPop(true);
popupEl.current.style.display = 'block';
console.log(placeId, placeToEdit);
};
<input className="edit-place-button" value="Edytuj" onClick={() => editPlace(place)} type="submit" />
then,
{ showPop &&
<div className="popup" ref={popupEl}>
<div className="button-popup-container">
<button type="submit" className="button-popup" onClick={() => { popupEl.current.style.display = 'none'; }}>X</button>
{/* line 102 here */}
<EditPlaceForm placeToEdit={placeToEdit} setShowPop={setShowPop}/>
</div>
</div>
}
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 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);
}
I creat multiple components, they have the same hierarchy, and inside they i also call other component call , now i want to create functions on my which will update the values i'm passing as props to my component. I manage to pass the functions as props, but can't manage to pass the value from child as parameters to the functions, so i could update only the props especific to that child.
App
function App() {
// eslint-disable-next-line
const [content, setContent] = useState(images)
const [count, setCount] = useState(content.votes)
console.log(count)
const upVote = (id) => {
alert('up =>', id)
}
const downVote = () => {
alert('down')
}
return (
<div className="App">
<div className="grid">
{content.map((picture, index) => {
return <Card key={index} picture={picture} teste={[upVote, downVote]}/>
})
}
</div>
</div>
)
}
Card
function Card({ picture, teste }) {
return (
<div className="card">
<div className="container">
<img
width="300"
alt={`id: ${picture.id}`}
src={picture.src}
className="image"
/>
<Options votes={0} originalPost={picture.original_post} teste={teste[0]}/>
</div>
</div>
)
}
Options
function Options({ votes, originalPost, teste }) {
const [count, setCount] = useState(votes)
const [styling, setStyling] = useState('#696969')
function countStyle(count) {
if (count > 0){
setStyling('#008000')
} else if (count < 0) {
setStyling('#B22222')
} else {
setStyling('#696969')
}
}
return (
<div>
<button onClick={() => teste(count)} className="buttons">teste</button>
<button title="Down vote" onClick={() => {
setCount(count - 1)
countStyle(count-1)
// style(count - 1)
}} className="buttons">-</button>
<span title="Vote counter" style={{color: styling}} className="counter">{count}</span>
<button title="Up vote" onClick={() => {
setCount(count + 1)
// style(count + 1)
countStyle(count +1)
}} className="buttons">+</button><br></br>
<a href={originalPost}
target="_blank"
title="Click to check the original post"
rel="noopener noreferrer"
className="link">Original post</a>
</div>
)
}
I would start by consolidating your state into the App component. Save the votes on your content array on each picture object. Pass the upvote and downvote functions down to each children and call them from your button clicks. I would also calculate the styling based on the props, rather than use state.
App
function App() {
let initialstate = images.map(image => {
image.votes = 0;
return image;
});
const [content, setContent] = useState(initialstate);
const upVote = index => {
setContent(content[index].votes + 1);
};
const downVote = index => {
setContent(content[index].votes - 1);
};
return (
<div className="App">
<div className="grid">
{content.map((picture, index) => {
return <Card key={index} picture={picture} index={index} upVote={upVote} downVote={downVote} />;
})}
</div>
</div>
);
}
Card
function Card({ index, picture, ...props }) {
const upVote = () => props.upVote(index);
const downVote = () => props.downVote(index);
return (
<div className="card">
<div className="container">
<img
width="300"
alt={`id: ${picture.id}`}
src={picture.src}
className="image"
/>
<Options votes={picture.votes} originalPost={picture.original_post} upVote={upVote} downVote={downVote}/>
</div>
</div>
)
Options
function Options({ votes, originalPost, upVote, downVote }) {
let styling = '#696969';
if (count > 0) {
styling = '#008000';
} else if (count < 0) {
styling = '#B22222';
} else {
styling = '#696969';
}
return (
<div>
<button title="Down vote" onClick={downVote} className="buttons">
-
</button>
<span title="Vote counter" style={{ color: styling }} className="counter">
{votes}
</span>
<button title="Up vote" onClick={upVote} className="buttons">
+
</button>
<br></br>
<a
href={originalPost}
target="_blank"
title="Click to check the original post"
rel="noopener noreferrer"
className="link"
>
Original post
</a>
</div>
);
}