I'm making a cart component where a user can add/delete products in a cart. Each time a button (Add to Cart) is clicked, the product should be added to the cart. When I try adding different types of products, the state resets each time to whatever product I clicked. But when I add multiple of the same product, the state updates accordingly, showing the product the same amount of times I had clicked. I need to be able to add different types of products to the cart. Any help is appreciated!
Displaying products on page:
export default function Products(props) {
const [cartItems, setCartItems] = useState([]);
//access first image from imageURLs section in object
const image = props.img;
const obj = {};
const result = Object.assign(obj, image);
//update cart
function addToCart(item) {
const updateCart = [...cartItems, props];
updateCart.forEach((e) => console.log("foreach", e));
setCartItems(updateCart);
}
console.log("new state", cartItems);
return (
<div className="product-container">
<img src={result[0]} alt="furniture" className="image" />
<div className="product-info">
<div className="product-name">{props.name}</div>
<div className="product-brand">{props.brand}</div>
<div className="product-price">${props.price}</div>
<button
type="submit"
className="add-button"
onClick={() => addToCart({ ...props })}
>
+ Add to Cart
</button>
</div>
</div>
);
}
Parent component:
import { useState } from "react";
import "../styles/Home.scss";
import "../styles/Products.scss";
import Products from "./Products";
export default function Home() {
const [product, setProduct] = useState([]);
//get product info from api
async function getProducts(e) {
e.preventDefault();
const data = await fetch(
`https://main-api.fulhaus.com/fulhaus-tech-test/get-products`
)
.then((res) => res.json())
.then((data) => data);
//set state for products
setProduct(data);
}
//display product info on page
function displayProduct() {
const productToDisplay = [
...new Set(
product.map((product, index) => (
<Products
key={product._id}
id={product._id}
img={product.imageURLs}
name={product.vendorProductName}
brand={product.vendorName}
price={product.MSRP}
/>
))
),
];
return productToDisplay;
}
return (
<div>
<div className="home-container"></div>
<div className="home-title">
<h1>Patio Furniture</h1>
<button
type="submit"
className="home-button"
onClick={(e) => getProducts(e)}
>
SHOP
</button>
</div>
<div className="product-section">{displayProduct()}</div>
</div>
);
}
The state resets since you remount the components on every render by invoking the displayProduct function on every render:
<div>{displayProduct()}</div>
Instead you should rewrite the logic to mount it once, the Set object is useless here (it contains objects, which you can't apply uniqueness to):
export default function Home() {
const [product, setProduct] = useState([]);
//get product info from api
async function getProducts(e) {
e.preventDefault();
const data = await fetch(
`https://main-api.fulhaus.com/fulhaus-tech-test/get-products`
)
.then((res) => res.json())
.then((data) => data);
//set state for products
setProduct(data);
}
return (
<div>
<div className="home-container"></div>
<div className="home-title">
<h1>Patio Furniture</h1>
<button
type="submit"
className="home-button"
onClick={(e) => getProducts(e)}
>
SHOP
</button>
</div>
<div className="product-section">
{product.map((product, index) => (
<Products
key={product._id}
id={product._id}
img={product.imageURLs}
name={product.vendorProductName}
brand={product.vendorName}
price={product.MSRP}
/>
))}
</div>
</div>
);
}
Related
I'm having a filter logic on the page. Clicking on different buttons I'm filtering the initial array. How can I display all the items in the array to the sibling component by clicking the "All" button in the filtering component. Need to pass function to the parent component if I'm getting it right.
https://codesandbox.io/s/trusting-moon-djocul?file=/src/components/Filters.js.
-----
Parent component
-----
const ShopPage = () => {
const [data, setData] = useState(Categories);
const filterResult = (catItem) => {
if (!catItem) {
console.log(Categories);
setData(Categories);
} else {
const result = Categories.filter(
(curData) => curData.category === catItem
);
setData(result);
}
};
return (
<>
<div className={styles.wrapper}>
<Filters filterResult={filterResult} />
<Products products={data} />
</div>
</>
);
};
export default ShopPage;
-----
Child component
-----
const Filters = ({ filterResult }) => {
return (
<>
<div className={styles.filterbtns}>
<div onClick={() => filterResult("Cap")} className={styles.filterbtn}>
Cap
</div>
<div onClick={() => filterResult("Shirt")} className={styles.filterbtn}>
Shirt
</div>
<div
onClick={() => filterResult("Jogging")}
className={styles.filterbtn}
>
Jogging
</div>
// needed to change the useState data of the compnonent and show all of the items
<div onClick={() => filterResult()} className={styles.filterbtn}>
All
</div>
</div>
</>
);
};
export default Filters;
**
Consider passing null as filterResult parameter from the all button:
<div onClick={() => filterResult(null)} className={styles.filterbtn}>
All
</div>
This can be captured in the filterResult function where you set the result back to the original Categories if no filter category was passed:
const result = catItem
? Categories.filter((curData) => curData.category === catItem)
: Categories;
Updated SandBox:
not getting quantity
whenever i push a product inside a cart, the product has been duplicated,
in single product page, if i add a cart then "add to cart" should be change into "go to cart"
this is my cartSlice page.
const CartSlice = createSlice({
name: "cart",
initialState: [],
reducers: {
add(state, action) {
state.push(action.payload);
},
remove(state, action) {
return state.filter((item) => item.id !== action.payload);
},
},
});
this is my singleProduct page
const SingleProduct = () => {
const dispatch = useDispatch();
const { data: products } = useSelector((state) => state.product);
const { productId } = useParams();
const product = products.find((product) => String(product.id) === productId);
const handleAdd = (product) => {
dispatch(add(product));
};
return (
<section className={style.SingleProductSection}>
<div className={style.btns}>
<button
className={style.addToCart}
onClick={() => {
handleAdd(product);
}}
>
<FaCartPlus />
<span>Add to cart</span>
</button>
<Link to="/buyNow">
<button className={style.buyNow}>
<AiFillThunderbolt /> <span>Buy Now</span> {/* buy now page */}
</button>
</Link>
</div>
);
};
here is my cart page
const Cart = () => {
const [total, setTotal] = useState();
const dispatch = useDispatch();
const carts = useSelector((state) => state.cart);
const handleRemove = (productId) => {
return dispatch(remove(productId));
};
useEffect(() => {
setTotal(
carts.reduce(
(acc, curr) => acc + Number(curr.price.toString().split(".")[0]),
0
)
);
}, [carts]);
return (
<>
{carts.map((product) => {
return (
<div className={style.product_cart} quantity={product.quantity}>
<img
src={product.image}
alt="product_image"
className={style.product_image}
/>
<p className={style.product_title}>
{product.title.substring(0, 18)}...
</p>
<p className={style.product_price}>
₹{product.price.toString().split(".")[0]}
</p>
<div className={style.product_quantity}>
<button className="decrement">-</button>
<p>{/* {quantity} */}0</p>
<button className="increment">+</button>
</div>
<button
onClick={() => {
handleRemove(product.id);
}}
>
<AiFillDelete className={style.product_delete_icon} />
</button>
</div>
);
})}
</div>
<div className={style.cartItem_2}>
<p className={style.product_total}>
<span>Total :</span>
<strong>₹{total}</strong>
</p>
<div className={style.cart_buyNow}>
<Link to="/buyNow">
<button className={style.buyNow}>
<AiFillThunderbolt /> <span>Buy Now</span> {/* buy now page */}
</button>
</Link>
</div>
</>
);
};
not getting quantity
whenever i push a product inside a cart, the product has been duplicated,
in single product page, if i add a cart then "add to cart" should be change into "go to cart"
not able to solve this problem,
It's hard to be sure that these are the exact causes but hopefully these points will help a bit.
not getting quantity - try using product.quantity here instead of just quantity
<button className="decrement">-</button>
<p>{/* {quantity} */}0</p> // <-- change here
<button className="increment">+</button>
whenever i push a product inside a cart, the product has been duplicated - you don't want to mutate the store in redux, so try this (remove is fine):
add(state, action) {
return [...state, action.payload];
},
add to cart/go to cart - you could do something like use a piece of state to track if the user has clicked add, if they haven't, do one thing, if they have, do another:
const SingleProduct = () => {
const dispatch = useDispatch();
const { data: products } = useSelector((state) => state.product);
const { productId } = useParams();
const product = products.find((product) => String(product.id) === productId);
// addition
const [hasClickedAdd, setHasClickedAdd] = useState(false)
// addition
const handleAdd = (product) => {
dispatch(add(product));
// addition
setHasClickedAdd(true);
// addition
};
return (
<section className={style.SingleProductSection}>
<div className={style.btns}>
<button
className={style.addToCart}
onClick={() => {
handleAdd(product);
}}
>
<FaCartPlus />
// addition
<span>{hasClickedAdd ? 'Buy now' : 'Add to cart'}</span>
// addition
</button>
<Link to="/buyNow">
<button className={style.buyNow}>
<AiFillThunderbolt /> <span>Buy Now</span> {/* buy now page */}
</button>
</Link>
</div>
);
};
nb, if you do this, you're probably also going to want to change the click handler so that clicking on the 'Buy now' text doesn't just add another one to the cart
I have 2 components, the Favorites component, makes a request to the api and maps the data to Card.
I also have a BtnFav button, which receives an individual item, and renders a full or empty heart according to a boolean.
Clicking on the BtnFav render removes a certain item from the favorites database.
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The data flow for now would be something like this:
Favorites component fetches all the complete data and passes it to the Card component, the Card component passes individual data to the BtnFavs component.
Favorites Component:
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>Mis favoritos</h1>
<Card listWines={vinosFavs} />
</div>
);
BtnFavs:
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The pattern you want is called a callback function, just like the onClick of a button. You pass a function to your components that get executed given a condition. If you want fetchWines to be called again, then just pass the function in as a prop.
Favorites Component:
<Card listWines={vinosFavs} refresh={fetchWines} />
Card Component
<BtnFavs onDelete={refresh} ... />
BtnFavs Component
onDelete();
You can name it whatever you want, but generally callbacks will be named like on<condition>.
If you really wanted useEffect to be triggered then you would pass a setState function that set one of the dependencies, but I don't see a point in this case.
I will share code, because this problem its normal for me, i really want to learn and improve that.
const Favorites = () => {
const { favoritesUser } = useFavoritesContext();
const user = useSelector((state) => state.user);
const id = user.id;
const [vinosFavs, setVinosFavs] = useState([]);
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>My favorits</h1>
<Grid listVinos={vinosFavs} />
</div>
);
};
export default Favorites
Grid
export default function Grid({ listVinos }) {
return (
<div>
<ul className={styles.layoutDeVinos}>
{listVinos?.map((element) => {
return <WineCard key={element.id} vino={element} />;
})}
</ul>
</div>
);
}
Card
export default function WineCard({ vino }) {
return (
<>
<div>
<Link to={`/products/${vino.id}`}>
<li>
<div className={styles.card}>
<div
className={styles.img1}
style={{
backgroundImage: `url(${vino.images})`,
}}
></div>
<div className={styles.text}>{vino.descripcion}</div>
<div className={styles.catagory}>
{vino.nombre}
<i className="fas fa-film"></i>
</div>
<div className={styles.views}>
{vino.bodega}
<i className="far fa-eye"></i>{" "}
</div>
</div>
</li>
</Link>
<div className="botonesUsuario">
<BtnFavs vino={vino} />
</div>
</div>
</>
);
}
BTN FAVS
export default function BtnFavs({ vino }) {
const { setFavoritesUser } = useFavoritesContext();
const [boton, setBoton] = useState(false);
const user = useSelector((state) => state.user);
const userId = user.id;
const productId = vino.id;
useEffect(() => {
axios
.post("/api/favoritos/verify", { userId, productId })
.then((bool) => setBoton(bool.data));
}, []);
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
return (
<>
{!user.id ? (
<div></div>
) : boton ? (
<span
class="favIcons material-symbols-rounded"
onClick={handleClickFav}
>
favorite
</span>
) : (
<span className="material-symbols-rounded" onClick={handleClickFav}>
favorite
</span>
)}
</>
);
}
I have this component that loads some data with buttons. I have a function that gets button via ID and updates state/variable to that buttons value, then loads data based on that. However since all the button IDs are the same, it only works for the first button...
I'm not sure how to set unique IDs for each button since they're generated through a map() function, and even if i did know, i'm not sure how my function would then target each button without writing a function for each one...
Edit: I've set unique IDs for buttons now, still need to solve the other problem.
return member.map((player, index) => (
<>
{" "}
<React.Fragment key={index}>
<br />{" "}
<div>
<br />
{player.map((stats) => (
<React.Fragment key={stats.player}>
<div class="flex-child">
<button id={'btn' + index} value={stats.player}>
{stats.player}
</button>
<p>{stats.role}</p>
<p>{stats.win_rate}</p>
<p>{stats.matches}</p>
<p>{stats.total_battles} Total Battles</p>
<p>{stats.score} / Points Earned</p>
</div>
</React.Fragment>
))}
</div>
<br />
</React.Fragment>
</>
));
};
export default SquadMembers;
here is index.js
import Head from "next/head";
import React, { useState } from "react";
import Teams from "../components/Teams";
import styles from "../../styles/Home.module.css";
import SquadMembers from "../components/SquadMembers";
import SquadData from "../components/SquadData";
export default function Home() {
const [teams, setTeams] = useState([]);
const [squads, setSquads] = useState([]);
const [player, setPlayer] = useState("Player Name");
const [squad, setSquad] = useState("default");
const [loading, setLoading] = useState(false);
function clicky() {
if (!document.getElementById("btn")) {
} else {
setPlayer(document.getElementById("btn").value);
loadPeople();
}
}
if (typeof window === "object") {
if (!document.getElementById("btn")) {
} else {
document.getElementById("btn").onclick = function () {
clicky();
};
}
}
function handleChange(e) {
setSquad(e.target.value);
}
const loadPeople = async () => {
setLoading(true);
const req = await fetch(`/api/player/${player}`);
const json = await req.json();
setTeams(json);
setLoading(false);
};
const loadSquad = async () => {
setLoading(true);
const req = await fetch(`/api/squad/${squad}`);
const json = await req.json();
setSquads(json);
setLoading(false);
setTeams([]);
};
return (
<div className={styles.main}>
<main className={styles.main}>
<h1>Silph Team Finder</h1>
<br />
<div>
<select value={squad} onChange={handleChange}>
<option value="default" selected disabled hidden>
Choose here
</option>
<option value="a342">Lots of options, cut them to save space</option>
</select>
<button onClick={() => loadSquad()}>Load</button>
<input
value={player}
id="player"
onChange={(e) => setPlayer(e.target.value)}
/>
<button onClick={() => loadPeople()} id="pbtn">
Load
</button>
{loading && <div className={styles.load}>LOADING</div>}
</div>
<div className={styles.teams}>
<SquadData squadz={squads} />
<Teams teams={teams} />
<div class="player-container">
<SquadMembers squadz={squads} />
</div>
</div>
</main>
</div>
);
}
Would be much easier to have something like:
<button value={stats.player} onClick={() => handleClick(stats.player)}>
{stats.player}
</button>
...
const handleClick = (player) => {
setPlayer(player);
loadPeople();
}
In this way you don't need id for button. Not only but you will avoid warning on same id for multiple elements.
Then I would like to talk about loadPeople(). If I understood correctly in this function you are using player that would be setted by setPlayer.
This is not a good approach. setPlayer is async and you could take an old value of player. Much better pass the last player value directly to loadPeople function. Something like:
const handleClick = (player) => {
setPlayer(player);
loadPeople(player);
}
const loadPeople = async (newPlayer) => {
setLoading(true);
const req = await fetch(`/api/player/${newPlayer}`);
const json = await req.json();
setTeams(json);
setLoading(false);
};
I got this component in React.js which make different kinds of filtering when I click a button, this is my code:
import React, { useContext } from 'react';
import { ModelsContext } from "../context/ModelsContext";
const FilterNav = () => {
const { modelos, guardarModelo } = useContext(ModelsContext);
const filterSegment = e => {
const segment = modelos.filter(modelo => modelo.segment === e.target.name);
guardarModelo(segment);
}
return (
<nav className="filter-container">
<div className="container">
<h3 className="filter-element-title">Filtrar por</h3>
<button type="button" className="filter-element">Todos</button>
<button type="button" className="filter-element" name="Autos" onClick={filterSegment}>Autos</button>
<button type="button" className="filter-element" name="Pickups y Comerciales" onClick={filterSegment}>Pickups y Comerciales</button>
<button type="button" className="filter-element" name="SUVs y Crossovers" onClick={filterSegment}>SUVs y Crossovers</button>
</div>
<p className="filter-element-last">Ordenar por ^</p>
</nav>
);
}
export default FilterNav;
The information I get from the api with useContext in ModelsContext.jsx, here is what I wrote so far:
import React, { createContext, useState, useEffect } from 'react';
export const ModelsContext = createContext();
const ModelsProvider = (props) => {
//State de modelos
const [modelos, guardarModelo] = useState([]);
const consultarAPI = async () => {
const api = await fetch("https://challenge.agenciaego.tech/models");
const modelos = await api.json();
guardarModelo(modelos);
}
//Cargar un modelo
useEffect(() => {
consultarAPI()
}, []);
return (
<ModelsContext.Provider
value={{
modelos,
guardarModelo
}}
>
{props.children}
</ModelsContext.Provider>
)
}
export default ModelsProvider;
My issue is that when I filter the API modelos throught the filterSegment function I don't know how to re-fetch the data from the API, because when I do a new call to the filterSegment function it filters the filtered data. I've tried to add a boolean state, and I was thinking about adding another state with allthedata, but I really lost about implementing it, I'm still very new to React.js.
I've search through stack overflow and google and I cannot get the answer, If you can give me a clue or some sort of guidance it will be appreciated.
Thanks so much!
You can add another state in the ModelsContext:
//State de modelos
const [modelos, guardarModelo] = useState([]);
const [allModelos, guardarAllModelo] = useState([]);
const consultarAPI = async () => {
const api = await fetch("https://challenge.agenciaego.tech/models");
const modelos = await api.json();
guardarAllModelo(modelos);
//uncomment if you want to have initial value for modelos state
//guardarModelo(modelos);
}
// some codes ...
<ModelsContext.Provider
value={{
allModelos,
modelos,
guardarModelo
}}
>
{props.children}
</ModelsContext.Provider>
Then in the FilterNav component:
const {allModelos, modelos, guardarModelo } = useContext(ModelsContext);
const filterSegment = e => {
const segment = allModelos.filter(modelo => modelo.segment === e.target.name);
guardarModelo(segment);
}
But this does not really re-fetch data from your web api. It just re-filters the first fetched data. if you want to re-fetch data from web api you can add consultarAPI in your context provider then call it somewhere.
Thanks code is working
This is my Portfolio gallery code First time load all data when click category then get category dataenter code here
Thanks code is working
This is my Portfolio gallery code First time load all data when click category then get category data`enter code here`
import React, { Component, useEffect, useState } from 'react'`enter code here`;
import Thumnailport_list from './Thumnailport_list';
import Portlightbox from './Portlightbox';
import Functional from './Functional';
import $ from 'jquery';
const Portfolio = () => {
const filterItem = async (categoryitem) => {
const updateitmes = allModelos.filter((curElm) => {
return curElm.categories === categoryitem
})
getporfolioState(updateitmes)
}
const [getporfolio, getporfolioState] = useState([])
const [allModelos, guardarAllModelo] = useState([]);
$(document).ready(function () {
$(".grid-wrap .grid li").unbind().click(function (e) {
console.log(this.className);
var newe = this.className;
$('.' + newe).addClass('current show');
$("#grid-gallery").addClass("slideshow-open");
});
$("#closeport").unbind().click(function (e) {
$("#grid-gallery").removeClass("slideshow-open");
$(".portfolio .grid li").removeClass('current show');
$(".portfolio .slideshow ul > li").removeClass('current show');
});
});
const portadd = () => {
document.body.classList.add('portfolio');
document.body.classList.add('at-top');
document.getElementById('port').classList.add('no-transform');
document.getElementById('port').classList.add('revealator-within');
document.getElementById('port2').classList.add('no-transform');
document.getElementById('port2').classList.add('revealator-within');
document.getElementById('navbar-collapse-toggle').classList.remove('biohidemenu');
}
const getalldata = async () => {
try {
const res = await fetch("/getdata", {
method: 'Get',
headers: {
'Content-Type': 'application/json'
}
})
const data = await res.json()
// console.log("This is our data load")
// console.log(data.portfolio)
getporfolioState(data.portfolio)
guardarAllModelo(data.portfolio)
} catch (error) {
console.log(error)
// history.push("/backoffice/login")
}
}
useEffect(() => {
getalldata()
portadd()
}, []);
return (
<>
<section id="port" class="title-section text-left text-sm-center revealator-slideup revealator-once revealator-delay1">
<h1 >my <span>portfolio</span></h1>
<span class="title-bg">works</span>
</section>
<section id="port2" className="main-content text-center revealator-slideup revealator-once revealator-delay1">
<div class="container">
<button className="btn btn-about " onClick={() => filterItem('mobileapp')}>Mobile</button>
<button className="btn btn-about " onClick={() => filterItem('frontend')}>Frontend</button>
<button className="btn btn-about " onClick={() => filterItem('gdesign')}>Graphics</button>
</div>
<div id="grid-gallery" className="container grid-gallery">
{/* Portfolio Grid Starts */}
<section className="grid-wrap">
<ul className="row grid">
{
getporfolio.map((getdata, index) => {
return (
<>
<Thumnailport_list
key={index}
portID={getdata._id}
imagetag={getdata.imguploadedFile}
figuertext={getdata.projectname}
/>
</>
)
})
}
</ul>
</section>
{/* Portfolio Grid Ends */}
{/* Portfolio Details Starts */}
<section className="slideshow" id="sdfer">
<ul>
{/* Portfolio Item Detail Starts */}
{
getporfolio.map((getdata, index) => {
return (
<>
<Portlightbox
idlight={getdata._id}
imagelight={getdata.imguploadedFile}
langport={getdata.language}
clientport={getdata.client}
projectnameport={getdata.projectname}
previewport={getdata.preview}
/>
</>
)
})
}
</ul>
{/* Portfolio Navigation Starts */}
<nav>
{/*<span className="icon nav-prev prev"><img src="images/left-arrow.png" alt="previous" /></span>
<span className="icon nav-next next"><img src="images/right-arrow.png" alt="next" /></span>*/}
<span className="nav-close" id="closeport"><img src="images/close-button.png" alt="close" /> </span>
</nav>
{/* Portfolio Navigation Ends */}
</section>
</div>
</section>
</>
)
}
export default Portfolio;