I'm working on an e-commerce site in React. I have a "Quantity" component, and I'm trying to get the quantity values to persist on the "Products" page after a user increases and/or decreases the quantities. I am doing this through local storage, but when I refresh the page, the value in local storage is updated on the first product only, and not the product for which I updated the quantity initially. Here's my "Quantity" component code:
import React, { useState, useEffect } from "react";
function Quantity({
counter,
setCounter,
inventory,
handleCounterDecrease,
handleCounterIncrease,
}) {
const [quantity, setQuantity] = useState(0);
const [inventoryWarning, setInventoryWarning] = useState("");
function increaseQuantity() {
setQuantity((quantity) => quantity + 1);
handleCounterIncrease();
if (inventory === quantity) {
setInventoryWarning("Quantity exceeds stock levels.");
setQuantity(quantity);
setCounter(counter);
}
}
function decreaseQuantity() {
setInventoryWarning("");
handleCounterDecrease();
setQuantity((quantity) => quantity - 1);
if (quantity < 1) {
setQuantity(0);
setCounter(counter);
}
}
useEffect(() => {
const data = JSON.parse(localStorage.getItem("quantity"));
if (data) {
setQuantity(data);
}
}, []);
useEffect(() => {
localStorage.setItem("quantity", JSON.stringify(quantity));
}, [quantity]);
return (
<div>
<div className="cart-quantity-container">
<i onClick={decreaseQuantity} class="minus square icon"></i>
<span>Quantity: {quantity}</span>
<i onClick={increaseQuantity} class="plus square icon"></i>
<p>{inventoryWarning}</p>
</div>
</div>
);
}
export default Quantity;
Related
I'm creating a Quiz app in Reactjs,
The issue is When I try to select the one option it get selected but if I try to select the other one it also gets selected. I want if A is selected and if the user clicked on B the A will be unselected and B will get selected.
I'd tried to do it in the child component as well as the parent component. In Child, I'm able to select one div but if I click on the other option they are also getting selected.
In Parent when I try to select any option it is selecting all of them.
Option.js (child component)
import React, { useState } from 'react'
function Option({ index, item, changeIndexValue }) {
const [currentOptionSelected, setCurrentOptionSelected] = useState(false)
const handleClick = (index) => {
changeIndexValue(index)
setCurrentOptionSelected(prevState => !prevState)
}
return (<>
<div className='option'
style={{ border: currentOptionSelected ? "2px solid red" : "1px solid black" }}
onClick={() => handleClick(index)}
key={index}
><p>{item}</p>
</div>
</>
)
}
export default Option
QuizMain.js (Parent component)
import React, { useState } from 'react'
import ProgressBar from '../ProgressBar/ProgressBar';
import './QuizMain.css'
import { useNavigate, useLocation } from 'react-router-dom';
import { useOutletContext } from "react-router-dom";
import ScoreCard from '../ScoreCard/ScoreCard';
import Option from '../Option/Option';
function QuizMain() {
const [currentQuestion, setCurrentQuestion] = useState(0)
const [changeCurrentQuestion, currentQuestionNumber, questionAre] = useOutletContext();
const [finalReport, setFinalReport] = useState([]);
const [finalReportVisible, setFinalReportVisible] = useState(false)
const [correctAnswer, setCorrectAnswer] = useState("")
const changeCorrectAnswer = (value) => {
setCorrectAnswer(value)
}
const [indexValue, setIndexValue] = useState("")
const changeIndexValue = (value) => {
setIndexValue(value)
}
const location = useLocation();
const { chooseQuizType } = location.state;
console.log(chooseQuizType, 'location')
const handleSubmit = () => {
const answersCurrentQuestion = questionAre[currentQuestionNumber].options.map((item) => item)
if (chooseQuizType == "withAnsers") {
console.log(answersCurrentQuestion[indexValue], 'selected option')
if (answersCurrentQuestion[indexValue] == questionAre[currentQuestionNumber].correctAnswer) {
alert('Correct answer')
} else {
alert('Wrong answer')
changeCorrectAnswer(questionAre[currentQuestionNumber].correctAnswer)
}
}
setFinalReport(prevState => [...prevState, {
question: questionAre[currentQuestionNumber].question,
correctAnswer: questionAre[currentQuestionNumber].correctAnswer,
yourAnswer: answersCurrentQuestion[indexValue]
}])
}
return (<>
{finalReportVisible ? <ScoreCard finalReport={finalReport} /> :
<div className='quizMain'>
<div className='quizMain__main'>
<div className='questions'>
<h3>{questionAre && questionAre[currentQuestionNumber]?.question}</h3>
</div>
<div className='options'>
{questionAre && questionAre[currentQuestionNumber]?.options.map((item, index) => {
return <Option
key={index}
item={item}
index={index}
changeCorrectAnswer={changeCorrectAnswer}
correctAnswer={correctAnswer}
indexValue={indexValue}
changeIndexValue={changeIndexValue}
/>
})}
<p>{correctAnswer}</p>
</div>
<div className='submit__button'>
<button onClick={handleSubmit}>Submit</button>
</div>
</div>
</div >
}
</>
)
}
export default QuizMain
In React, you should avoid putting too many logic in child components. Child components should be used to display information, let parent container handle logics.
In QuizMain.js (Parent), define a state to controll which option is currently selected
const [selected, setSelected] = useState(-1); // -1 means non is selected
also define a function which will be passed to Option component (Child) to update selection
const selectOption = (option) => {
setSelected(option);
}
In Option.js (Child), include the following to props
function Option({ index, item, onSelect, selected })
for the onClick callback of Option.js, just put onSelect(index) to it
onPress={ () => onSelect(index) }
Back to QuizMain.js, each Option component should be like
<Option
...
key={ index }
selected={ selected === index }
/>
I am trying to use an Axios get to get an object that contains all of the information from a single row of a Postgres table that matches the name of a specific book for a "book selling" website. The book name is stored in a localStorage array that is taken from a previous shop page where the user chooses the books they want to buy and the localStorage array of "book names" is then passed into the Axios get in the loop. My problem is within the first Axios get (axios.get(http://localhost:3001/cart/${book})), I believe my code overwrites the component each time and only 1 component is displayed instead of 3 or 4 depending on the number of items that are in the cart. I looked at the React documentation and know I have to use the spread operator but when I tried using it as described in the documentation, it started creating arrays within arrays and broke my page. I know I'm doing something wrong but I'm not familiar enough with React to solve it. Thanks for any help and sorry for the messy code and all the console.log lines, I am still testing!
import React, { useState, useEffect } from "react"; import axios from 'axios'; import '../styles/Cart.css'; import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'; import { faCartShopping, faPlus, faMinus, faTimes } from '#fortawesome/free-solid-svg-icons';
function Cart() {
const [cart, setCart] = useState([]);
let currentcart;
let currentuser;
let cartstatus;
let empty = "empty";
currentcart = JSON.parse(localStorage.getItem("currentCart"));
currentuser = JSON.parse(sessionStorage.getItem('currentUser'));
cartstatus = localStorage.getItem("cartStatus");
let d = new Date();
let date = d.toLocaleString();
console.log(currentcart);
console.log(cartstatus);
const BookItem = ({
book_name,
price,
book_img
}) => (
<div classname='shopInventory'>
<div id='bookCard'>
<img src={book_img} width=
'100%' height='100%'/>
<h3>{book_name}</h3>
${price}
<br/><br/>
<button className="addBtn"><FontAwesomeIcon icon={faPlus}/> Add</button>
<span id='bookQuantity'>0</span>
<button className='minusBtn'><FontAwesomeIcon icon={faMinus} /> Subtract</button>
</div>
</div>
);
useEffect(() => {
currentcart.map((book) =>
axios.get(`http://localhost:3001/cart/${book}`).then((response) => {
const data = response.data
setCart(data)
})
.catch((error) => {
console.log(error)
})
)}, [currentcart.length]);
let orderData ={
username: currentuser,
order: currentcart,
date: date
};
const submitButton = (event) => {
axios.post(`http://localhost:3001/checkout`,orderData).then((response)
=> {
})
.catch((error) => {
console.log(error)
})
}
const clearCart = () => {
cartstatus = localStorage.setItem('cartStatus', empty);
window.location.reload();
}
console.log(cart);
return (
<>
{cartstatus != "empty" ? (
<div>
<div className='cartSection'>
<div>
{cart.map(cart => <BookItem key={cart.book_name} {...cart} />)}
</div>
</div>
<div id='clear-both'></div>
<div id='checkoutBtns'>
<button className='checkoutBtn' onClick={submitButton}><FontAwesomeIcon icon={faCartShopping}/> Checkout</button>
<button className='clearBtn' onClick={clearCart}><FontAwesomeIcon icon={faTimes}/> Clear Cart</button>
</div>
<div id='clear-both'></div>
</div>
) : (
<center>
<h3>
The Cart is empty
</h3>
</center>
)}
</>
); }
export default Cart;
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>
);
}
I need some help with incrementing a value through map function while using React's context API. Here is an example to better understand:
Let's say I have items:
const [items, setItems] = useContext(ItemsContext)
These items are JSON objects inside an array.
And then I want to return each item's properties in a list but some of them modified - for example, the item has quantity and I want to increment/decrement it on click. How do I achieve this individually for every item?
I tried making a local state for the quantities:
const [quantity, setQuantity] = useState([])
,so I have all the quantities of all elements but it got me nowhere.
The thing I am trying to accomplish is similar to this:
<div>
<ul>
{
items.map(item => (
<li>
<p>item.name</p>
<p>item.quantity</p>
<button onClick={incQuantity}> </button>
</li>
}
</ul>
</div>
Edit:
const [idCounter, setIdCounter] = useState(0)
I use props. here because this is another component.
const addItem = () => {
if (quantity > 0) {
setIdCounter(idCounter + 1)
setItems(prevItems => [...prevItems, {id: idCounter, name: props.name, price: props.price, quantity: quantity }])
}
}
And I implemented the handler quite the same:
const quantityHandler = (id, diff) => {
setItems(items.map((item) =>
item.id === id ? {...item, quantity: item.quantity + diff} : item
))
}
And here is the list itself:
<div>
<ul>
{
items.map(item => (
<li>
<p>item.name</p>
<p>item.quantity</p>
<button onClick={() => quantityHandler(item.id, 1)}> </button>
<button onClick={() => quantityHandler(item.id, -1)}> </button>
</li>
}
</ul>
</div>
Here is working example and I will explain it a little: in App we make MyContext and state with hook, then we provide state and function to update state to Context provider as value. Then in any place inside Provider we have access to that state and setter. We render items and we can update them using hook setter from Context.
import React, { useState, useContext } from "react";
const MyContext = React.createContext(null);
const initialState = [
{ id: 1, quantity: 1 },
{ id: 2, quantity: 2 },
{ id: 3, quantity: 3 },
{ id: 4, quantity: 4 },
];
const DeepNestedComponent = () => {
const [stateFromContext, setStateFromContext] = useContext(MyContext);
// MyContext should be imported
const buttonHandler = (id, diff) => {
setStateFromContext(
stateFromContext.map((item) =>
item.id === id ? { ...item, quantity: item.quantity + diff } : item
)
);
};
return (
<div>
{stateFromContext.map(({ id, quantity }) => (
<div key={id}>
{quantity}
<button onClick={() => buttonHandler(id, 1)}> + </button>
<button onClick={() => buttonHandler(id, -1)}> - </button>
</div>
))}
</div>
);
};
const App = () => {
const [contextState, setContextState] = useState(initialState);
return (
<MyContext.Provider value={[contextState, setContextState]}>
<DeepNestedComponent />
</MyContext.Provider>
);
};
export default App;
Like it if its is working )
I am trying to dispatch my action when the client clicks the "addToCart" btn. which will add a new product to the cart, but i get the following error: "TypeError: props.addToCart is not a function".
I am quite new to Redux and have learned the basics, but i cant seem to fix this problem.
code cartActions:
import * as actions from '../constants/cartConstants'
import store from '../storeRedux'
console.log(store.getState())
//go through all the items and add the item with the specific id
//with getState we can get whatever exists in the redux store
export const addToCart = (product,qty,count) =>(dispatch)=> {
let exists = false
const cartItems = store.getState().cart.cartItems.slice()
cartItems.forEach(item=> {
if(item.id === product.id){
exists = true
item.qty++
count++
qty++
}
})
if(!exists){
cartItems.push({...product, count : 1, qty: 1})
}
dispatch({
type: actions.ADD_TO_CART,
payload: {cartItems}
})
localStorage.setItem("cartItems", JSON.stringify(cartItems))
}
export const removeFromCart = (product)=>(dispatch) =>{
const cartItems = store.getState()
.cart.cartItems.slice()
.filter(
x => x.id !== product.id
)
dispatch({
type: actions.REMOVE_FROM_CART,
payload: {cartItems}
})
localStorage.setItem("cartItems",JSON.stringify(cartItems))
}
export const adjustQty = (product,qty)=> (dispatch)=> {
dispatch({
type: actions.ADJUST_QTY,
payload: {
product,
qty
}
})
}
export const reset =(cartItems,qty,count)=> (dispatch)=> {
dispatch({
type: actions.RESET,
payload: {
cartItems,
qty,
count
}
})
}
Code cartReducer:
import * as actions from '../constants/cartConstants'
const initialState = {
cartItems: JSON.parse(localStorage.getItem("cartItems")) || [] ,
count:0,
qty: 0,
amount: 0
}
const shopReducer = (
state = initialState,action)=> {
switch(action.type){
case actions.ADD_TO_CART:
return{
...state,
cartItems:action.payload.cartItems,
count: action.payload.count,
qty: action.payload.qty
}
case actions.REMOVE_FROM_CART:
return {
...state,
cartItems:action.payload.cartItems,
count: action.payload.count
}
case actions.RESET:
return{
...state,
cartItems: action.payload.cartItems =[],
qty: action.payload.qty =0,
count: action.payload.count =0,
amount: action.payload.amount =0
}
default:
return state;
}
}
export default shopReducer
Code productPage:
import React, { useEffect } from 'react'
import Nav from '../components/nav'
import '../styles/productdetails.css'
import {connect} from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'
import { detailsProduct } from '../../actions/productActions'
import Review from '../components/review'
import { addToCart } from '../../actions/CartActions'
function ProductPage(props){
//manage quantity product
const productDetails = useSelector(state=> state.productDetails)
const{product,loading,error} = productDetails
const dispatch = useDispatch();
const cart = useSelector(state=> state.cart)
const {qty} = cart
useEffect(()=> {
dispatch(detailsProduct(props.match.params.id))
}, [])
return(
<div>
<Nav/>
<a className="product-a" href="/store">Back to products</a>
{loading? <div>loading...</div>: error? <div>{error}</div>:
(
<div className="productpage">
<div className="img" style={{background: `url(${product.img})`, backgroundSize: 'cover'}}></div>
<div className="description">
<h1>{product.name}</h1>
<p>{product.description}</p>
<span><small>€</small>{product.price}</span>
<div className="amount">
<p>Number:</p>
<label>
<button type="button" className="btnInc" onClick={()=> {}}>+</button>
<input type="number"step="1" min="1" value={qty} />
<button type='button' className="btnDec" onClick={()=> {}}>-</button>
</label>
<div>Size: {product.size}</div>
</div>
{product.qty > 0? <button type="submit" className="addBtn" onClick={()=> {props.addToCart(product)}}> Add to cart</button> : <div>Out of stock</div>}
</div>
</div>
)}
<Review/>
<div className="reviews">
<h3>username</h3>
<p>reviews : 3 out of 5</p>
<p>description of what he says</p>
</div>
</div>
)
}
export default connect(null,addToCart)(ProductPage)
You should do the following:
export default connect(null,{addToCart})(ProductPage)
According to the documentation the mapDispatchToProps parameter can either be a function or an object (or undefined).
If it is a function then it is expected to return an object and each property of that object is expected to be a function that can be called by your component to dispatch an action.
If it is an object then each property of that that object should be a function that returns an action (action creator). Connect will replace that function with a function that will pass arguments to the action creator and dispatches the resulting action.
So {addToCart} is shorthand for: {addToCart: addToCart} which is an object with an addTocart property that has a value of the action creator named addToCart.