How to make add to cart page using reactjs and redux - javascript

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

Related

Why isn't the todo being deleted?

I am working through a tutorial for a course I'm taking. The lab I'm working on walks through creating a to-do app. I'm on step 3, which asks us to create a button that deletes a task. I feel ridiculous, because I know I can figure it out but...well, I haven't yet! I will post the code to see if there are any initial issues, and then update with the methods I've already tried. Any help is greatly appreciated. Thank you!
import React, { useState } from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button}</div>)}
</div>
);
};
export default App;
I didn't just copy and paste, so it's possible that I messed something up while typing. I'm expecting the deleteTodo() function to accept a todo.id and filter the list of todos, excluding the one I want to delete. I'm thinking that the issue may be cause by the way I've created the button? Again, I'm not sure why I can't figure it out. TIA.
EDIT: Okay, it works now! Thank you all so much for explaining this. For anyone else that comes across this problem, here's where I mis-stepped:
const button = <button onClick={() => deleteTodo(todo.id)}Delete<button>
#Nicholas Tower's explanation was very clear--creating this outside of .map(...)causes deleteTodo to get the todo state, not the not the todo I want it to delete from the todos array. #Lars Vonk, #0stone0, and #Sudip Shrestha all said this as well. #Sudip Shrestha and #pilchard also helped correct the deleteTodo function. Again, I really appreciate all the help. The code works now. I'll show the updates so people having a similar issue can compare:
import React from "react";
import "./App.css";
const App = () => {
const [todos, setTodos] = React.useState([]);
const [todo, setTodo] = React.useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos(todos.concat(newTodo));
setTodo("");
} else {
alert("Enter a valid task");
setTodo("");
}
}
// update the state using setState, rathar than mutating it directly #Sudip Shrestha
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo.id !== id)
});
};
// line 51: button placed inside .map(), as per many suggestions below.
return (
<>
<h1>Todo List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task..."
value={todo}
/>
</form>
{todos.map((todo) =>
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>)}
</>
);
};
export default App;
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You're creating this button element just once, and the todo variable it refers to is the todo state, which is a string (usually an empty string). Since todo is a string, todo.id is undefined, and deleteTodo can't do anything with that.
You need to create separate buttons for each item, so you should move this code down into your .map:
{todos.map((todo) => (
<div>
ID: {todo.id} Task: {todo.text}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
))}
Now each item has its own button, with its own onClick function. And in those functions, todo is the item of the array.
The button cannot access which todo it has I think you should put the code from the const button where you are referring to it or by changing it to const button = (todo) => <button onClick={ () => deleteTodo(todo.id); }>Delete</button> and access it by doing {button()}
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
This has the same callBack for each todo, you should move this inside your map so that todo.id refers to the iterator of the map():
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
Updated Demo:
const { useState } = React;
const App = () => {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
};
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo));
setTodo("");
} else {
alert("Enter Valid Task");
setTodo("");
}
}
const deleteTodo = (id) => {
let updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map((todo) => (
<React.Fragment>
<div>ID: {todo.id} Task: {todo.text}</div>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</React.Fragment>
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Try this:
const button = (t) => <button onClick={() => deleteTodo(t.id)}>Delete</button>
and then, in the map
{todos.map((todo) => <div>ID: {todo.id} Task: {todo.text} {button(todo)}</div>)}
this way, the "delete todo" button will be bound to the specific todo ID, avoiding being bound to whatever the current value of todo is in the app.
Its better to update the state using setState. Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
Also You need to change the delete from string to function and pass the id or place the jsx directly inside map function.
import React, { useState } from 'react'
const App = () => {
const [todos, setTodos] = useState([])
const [todo, setTodo] = useState('')
const handleSubmit = e => {
e.preventDefault()
const newTodo = {
id: new Date().getTime(),
text: todo.trim(),
completed: false,
}
if (newTodo.text.length > 0) {
setTodos([...todos].concat(newTodo))
setTodo('')
} else {
alert('Enter Valid Task')
setTodo('')
}
}
/*
* Changed Here
*/
const deleteTodo = id => {
setTodos(prevState => {
return prevState.filter(todo => todo?.id != id)
})
}
const button = id => <button onClick={() =>
deleteTodo(id)}>Delete</button>
return (
<div>
<h1>To-do List</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={e => setTodo(e.target.value)}
placeholder="Add a new task"
value={todo}
/>
<button type="submit">Add Todo</button>
</form>
{todos.map(todo => (
<div key={todo.id}>
ID: {todo.id} Task: {todo.text} {button(todo.id)}
</div>
))}
</div>
)
}
export default App
Problem in:
const button = <button onClick={() => deleteTodo(todo.id)}>Delete</button>
You can use
const Button = (props) => {
return (
<button
className={`btn ${props.className}`}
title={`${props.title}`}
onClick={props.onClick ? () => props.onClick() : null}
>
{props.children}
</button>
);
};
after that, call it like this
<Button className="delete" title="delete" onClick={()=>deleteTodo(todo.id)}>Delete</Button>

How to correctly delete a dynamically added div panel from an array in React

I am trying to add a delete function for my ConnectedProducts.js page, but can't seem to pass the correct ID to my Modal which handles the delete function. The delete function should delete a div panel (which are saved in an array) from said page, and works by clicking a button on the page which opens up a Modal asking for confirmation before deleting.
I am not exactly sure how I even get the correct ID from my array since the divs are added to the array with the .map function. The delete function seems to atleast somewhat work since it takes an index number and deletes the specified indexed div. (Shortened the following code to a minimal working example)
ConnectedProducts.js:
import React from "react";
import "../Overview.css";
import Approve from "../icons/icon-dc_alert-success-filled.svg";
import Denied from "../icons/icon-dc_callout_hint.svg";
import Close from "../icons/icon-dc_close.svg";
import Add from "../icons/icon-dc_add.svg";
import ModalAddProduct from "../components/ModalAddProduct.jsx";
import { Link } from "react-router-dom";
import ModalDelete from "../components/ModalDelete.jsx";
import ModalProductSettings from "../components/ModalProductSettings.jsx";
const ConnectedProducts = () => {
const [stateComment, setStateComment] = React.useState("");
const [open, setOpen] = React.useState(false);
const [openDelete, setOpenDelete] = React.useState(false);
const [openSettings, setOpenSettings] = React.useState(false);
const [states, setStates] = React.useState([]);
const handleComment = (e) => {
setStateComment(e.target.value);
};
function getIndex() {
// What exactly do I need to return here? If I return 1 for an example I need to add
// two panels, and only the second panel would get deleted while the first one stays no matter what
// panel I want to delete
return 1;
}
return (
<div id="wrapper">
{open && <ModalAddProduct setOpen={setOpen} setStates={setStates} />}
{openDelete && <ModalDelete setOpen={setOpenDelete} setStates={setStates} states={states} index={getIndex()} />}
{openSettings && <ModalProductSettings setOpen={setOpenSettings} states={states} setStates={setStates} index={getIndex()} />}
<div class="component-headline">
<h1 style={{ textAlign: "center" }}>
[APPLICATION NAME] - Connected Products:
</h1>
</div>
<div class="center-product-content">
<div id="center_connect">
//Here the divs get added to the "states" array after clicking Add on the Modal
{states.map((state, index) => {
return (
<div key={index}>
{state.stateSelect !== "Select Product Type" && state.stateSelect !== "" && (
<div class="app-box" key={index}>
<img
class="image"
alt="Logo"
src="https://st.depositphotos.com/1968353/2535/i/600/depositphotos_25357041-stock-photo-close-up-of-machine-gears.jpg"
/>
<div class="box-content">
<h3 class="product-h3"> {state.state} </h3>
<textarea
class="product-textarea"
placeholder="Short description of Product; max. 280 characters"
readOnly={true}
onChange={(e) => handleComment(e)}
value={state.stateComment}
>
{state.stateComment}
</textarea>
<h3 class="product-h3-second"> Configuration </h3>
<div class="approve-img">
<img
class="product-image"
alt="Icon"
src={Approve}
></img>
</div>
<div
class="button-content new"
id="product-delete-button"
>
<img
class="close_dyn"
alt="Delete"
src={Close}
onClick={() => setOpenDelete(true)}
></img>
</div>
<div class="module-button center_button_dyn">
<button
type="button"
class="btn btn-secondary"
onClick={() => setOpenSettings(true)}
></button>
<div class="button-animation"></div>
<div class="button-content">
<span class="content-text">Configure</span>
</div>
</div>
</div>
</div>
)}
</div>
);
})}
</div>
<div
style={{ cursor: "pointer" }}
class="app-box"
onClick={() => setOpen(true)}
>
<img
class="image"
src={Add}
alt="Add"
style={{ height: "150px", position: "relative", left: "550px" }}
/>
<p
style={{ fontSize: "larger", position: "relative", left: "600px" }}
>
Add Connected Product
</p>
</div>
<div class="module-button" style={{ left: "1340px" }}>
<Link to="/overview">
<button type="button" class="btn btn-secondary"></button>
</Link>
<div class="button-animation"></div>
<div class="button-content">
<span class="content-text">Done</span>
</div>
</div>
</div>
</div>
);
};
export default ConnectedProducts;
ModalDelete.jsx:
import React from "react";
const ModalDelete = ({ setOpen, setOpenSettings, index, states, setStates }) => {
React.useEffect(() => {
function handleEscapeKey(event) {
if (event.code === "Escape") {
setOpen(false);
}
}
document.addEventListener("keydown", handleEscapeKey);
return () => document.removeEventListener("keydown", handleEscapeKey);
});
//Delete Function
const deleteProduct = (index) => {
const copy = [...states];
copy.splice(index, 1);
setStates(copy);
console.log(index)
setOpen(false);
setOpenSettings(false);
}
return(
//HTML shortened up to the responsible button click for the delete function
<button
type="button"
class="btn btn-light btn"
onClick={() => deleteProduct(index)}
></button>)
export default ModalDelete;
ModalAddProduct.jsx: (adds the panel to ConnectedProducts.js)
import React from "react";
const ModalAddProduct = ({ setOpen, setStates }) => {
const [ModalState, setModalState] = React.useState("");
const [ModalStateComment, setModalStateComment] = React.useState("");
const [ModalSelect, setModalSelect] = React.useState("");
React.useEffect(() => {
function handleEscapeKey(event) {
if (event.code === "Escape") {
setOpen(false);
}
}
document.addEventListener("keydown", handleEscapeKey);
return () => document.removeEventListener("keydown", handleEscapeKey);
});
const handleComment = (e) => {
setModalStateComment(e.target.value);
};
const handleChange = (e) => {
setModalState(e.target.value);
};
const handleSelect = (e) => {
setModalSelect(e.target.value);
}
//Function to add new panels
const addNewProduct = () => {
setOpen(false);
setStates( oldArray => [...oldArray, {
state: ModalState,
stateComment: ModalStateComment,
stateSelect: ModalSelect,
}])
};
...
export default ModalAddProduct;
What am I missing to dynamically delete any selected panel? I tried so many different approaches to this, but can't seem to find a working solution.
import uuid from "uuid/v4";
const addNewProduct = () => {
setOpen(false);
setStates( oldArray => [...oldArray, {
state: ModalState,
stateComment: ModalStateComment,
stateSelect: ModalSelect,
id:uuid()
}])
};
//this will add id to each added item
const deleteProduct = (id) => {
"Set Your state"((prevItem) => {
return prevItem.filter((item) => item.id != id);
});
};
//In delete, function it filters the state and check whether the id is there or not
//let me know if it works

After 2nd render page got disappears after a flash

I am building react app using express js, axios, and Mongo DB. When I navigate from the home page to the detailed page (by clicking overlay) , react renders the detailed page twice and after 2nd time page disappears in a flash. Here are the pages.
Home page
const Post = ({ post, setCurrentId }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const user = JSON.parse(localStorage.getItem("profile"));
const postDetails = (id) => {
navigate(`posts/${id}`);
};
const deletePostHandler = () => {
if (post.creator === user.id || post.creator === user.sub) {
dispatch(deletePost(post._id));
}
};
return (
<div className="post" key={post.id}>
<div className="user-details">
<img src={post.selectedFile} alt="" title={post.title} />
<div className="overlay" onClick={() => postDetails(post._id)}></div> // here is the link to detailed page passing id to retrive specific details
<div className="top-left">
<span className="user-name">{post.name}</span>
<span className="time">{moment(post.createdAt).fromNow()}</span>
</div>
<button className="edit-button" onClick={() => setCurrentId(post._id)}>
<MoreHorizIcon></MoreHorizIcon>
</button>
</div>
<div className="details">
<span className="tags">
{post.tags[0].split(",").map((tag) => "#" + tag.trim() + " ")}
</span>
<span className="title">{post.title}</span>
<span className="message">{post.message}</span>
</div>
<div className="button-container">
<button
className="like-button"
onClick={() => dispatch(likePost(post._id))}
>
<ThumbUpIcon className="thumbUpIcon"></ThumbUpIcon>
{/* <ThumbUpOffAltIcon></ThumbUpOffAltIcon> */}
<span>
{post.likes.length > 1
? `Likes ${post.likes.length}`
: `Like ${post.likes.length}`}
</span>
</button>
<button className="delete-button" onClick={deletePostHandler}>
<DeleteIcon></DeleteIcon>
</button>
</div>
</div>
);
};
export default Post;
Detailed page
const PostDetails = () => {
const dispatch = useDispatch();
const { id } = useParams();
useEffect(() => {
dispatch(getPost(id));
}, []);
const { post } = useSelector((state) => state.posts);
if (!post) return null;
return (
<div className="post-detail-container">
<div className="post-details">
<div className="title">{post.title}</div>
<div className="tags">
{post.tags[0].split(",").map((tag) => "#" + tag.trim() + " ")}
</div>
<p readOnly className="message">
{post.message}
</p>
<div className="createdBy">Created by: {post.name}</div>
<div className="createdBy">{moment(post.createdAt).fromNow()}</div>
</div>
<img
className="post-image"
src={post.selectedFile}
alt=""
title={post.title}
/>
</div>
);
};
postReducer
import { POST_ACTION_TYPES } from "../../action/post/postActionTypes";
export const postReducer = (state = [], action) => {
//state always will be posts
switch (action.type) {
case POST_ACTION_TYPES.FETCH_ALL:
return action.payload;
case POST_ACTION_TYPES.FETCH_POST:
return { ...state, post: action.payload };
case POST_ACTION_TYPES.CREATE:
return [...state, action.payload];
case POST_ACTION_TYPES.UPDATE:
case POST_ACTION_TYPES.LIKE:
return state.map((post) =>
post._id === action.payload._id ? action.payload : post
);
case POST_ACTION_TYPES.DELETE:
return state.filter((post) => post._id !== action.payload);
default:
return state;
}
};
Please explain why is that and help me to fix it.
Can you provide the logs?
Check the browser console for any errors, it may be that post is an array so you've to check if the array is empty by array.length < 1 ? return null : return (<Component/>) or you can use lodash _. isEmpty() . An empty array is a truthy value in javascript so if(!post) will return false

How to keep already chosen value in ReactJS

I have following code.
What I'm trying to do is, in the first step, select one of the elements, store it in my state, and in the last step, console.log all my data. Also, the user can go from the last step to the first and change what he chose before. But the problem is that I can't save what the user selects for the first time.
For example, if the user selects the second one, and then on the last step they go back, then the first one is displayed as selected. How can I fix this?
here is my code
App.js
const [current, setCurrent] = useState(0);
const [data, setData] = useState({
firstName: "AAA",
lastName: "BBB",
age: 26
});
const steps = [
{
content: (
<PackageChoose setCurrent={setCurrent} data={data} setData={setData} />
),
id: 0
},
{
content: <LastStep setCurrent={setCurrent} data={data} />,
id: 1
}
];
return (
<div className="App">
<div>{steps[current].content}</div>
</div>
);
packageChoose (or first step)
const PackageChoose = ({ setCurrent, data, setData }) => {
const [selected, setSelected] = useState(1);
const [packageType, setPackageType] = useState(data.package || "choice");
return (
<div>
<div
onClick={() => {
setPackageType("choice");
setData({ ...data, packageType: packageType });
}}
>
<SelectCard
id={1}
selected={selected}
onSelect={setSelected}
text="text 1"
/>
</div>
<div
onClick={() => {
setPackageType("select");
setData({ ...data, packageType: packageType });
}}
>
<SelectCard
id={2}
selected={selected}
onSelect={setSelected}
text="text 2"
/>
</div>
<button onClick={() => setCurrent(1)}>Next</button>
</div>
);
};
Last step
const LastStep = ({ setCurrent, data }) => {
return (
<div>
LastStep
<button
onClick={() => {
setCurrent(0);
}}
>
Previous
</button>
<button onClick={() => console.log("data===>", data)}> submit </button>
</div>
);
};
Selected Card reusable component
const SelectCard = ({ id, selected, onSelect, text }) => {
const myClassName =
id === selected
? Styles.selectCardWrapperActives
: Styles.selectCardWrapper;
return (
<div className={classNames(myClassName)} onClick={() => onSelect(id)}>
<div> {text} </div>
</div>
);
};
Please help me to fix this problem.
You can move the selected state in PackageChoose to App level.
In App.js define the selected state and pass as props.
export default function App() {
const [selected, setSelected] = useState(1);
...
...
<PackageChoose
...
...
selected={selected}
setSelected={setSelected}
/>
}
In PackageChoose use the props passed above and remove the local selected state.
const PackageChoose = ({ setCurrent, data, setData, setSelected, selected }) => {
You need to update the packageType inside onClick handler. Since setState calls are batched and enqueued inside event handler and state updates may be asynchronous. You can't access the packageType state immediately after setting it.
PackageChoose.js
Card 1
onClick={() => setData({ ...data, packageType: "choice" })}
Card 2
onClick={() => setData({ ...data, packageType: "select" })}
set the packageType directly on data.

How to keep previous states when adding new ones

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>
);
}

Categories

Resources