I am doing a React JS Cart and I am having problems when I try to delete an Item from the there. It has already a function that adds the items and also another for the total quantity and the total price.
This is the ContextProvider:
import { useState } from "react";
import { CartContext } from "./CartContext";
export const CartProvider = ({ children }) => {
const [list, setList] = useState([]);
const addCart = (varietalCount) => {
if (list.find((item) => item.id === varietalCount.id)) {
const newVarietal = list.map((varietal) => {
if (varietal.id === varietalCount.id) {
return { ...varietal, count: varietalCount.count + varietal.count };
}
return varietal;
});
setList(newVarietal);
} else {
setList((state) => {
return [...state, varietalCount];
});
}
};
console.log("list", list);
// const deleteProd = (varietalCount) => {
// if (list.find((item) => item.id === varietalCount.id)) {
// const deleteVarietal = list.map((varietal) => {
// if (varietal.id === varietalCount.id) {
// return { ...varietal, count: null };
// }
// return varietal;
// });
// setList(deleteVarietal);
// } else {
// setList((state) => {
// return [...state, varietalCount];
// });
// }
// };
const totalPrice = () => {
return list.reduce((prev, next) => (prev + (next.count * next.price)), 0)
};
const totalQuantity = () => {
return list.reduce((prev, next) => (prev + (next.count)), 0)
};
return(
<>
<CartContext.Provider value={{ list, addCart, totalPrice, totalQuantity }}>
{children}
</CartContext.Provider>
</>);
};
If it is necessary I can add to the post the Cart.js or the ItemDetail.js. I hope someone can help me. Cheers
I think you can just use filter given that your state has value of an array. Something like:
const deleteProd = (varietalCount) => {
const newItems = list.filter((item) => item.id !== varietalCount.id)
setList(newItems);
};
You can check more array functions from here https://www.w3schools.com/jsref/jsref_obj_array.asp
Related
I'm using ink package, and I'm building the following component:
MultiSelect.tsx file:
import React, { useEffect, useState } from 'react';
import { useStdin } from 'ink';
import type { ISelectItem, ISelectItemSelection } from './interfaces/select-item';
import { ARROW_DOWN, ARROW_UP, ENTER, SPACE } from './constants/input';
import MultiSelectView from './MultiSelect.view';
interface IProps {
readonly items: ISelectItem[];
readonly onSubmit: (selectedItems: string[]) => void;
}
const MultiSelect: React.FC<IProps> = (props: React.PropsWithChildren<IProps>) => {
const { stdin, setRawMode } = useStdin();
const [itemsSelectionState, setItemsSelectionState] = useState<ISelectItemSelection[]>(
props.items.map((item, index) => ({ ...item, selected: false, isHighlighted: index === 0 })),
);
const stdinInputHandler = (data: unknown) => {
const rawData = String(data);
if (rawData === ARROW_DOWN) {
setItemsSelectionState((prev) => {
const highlightedIndex = prev.findIndex((item) => item.isHighlighted);
if (highlightedIndex === -1) {
return prev;
}
const clonedPrev = structuredClone(prev);
clonedPrev[highlightedIndex]!.isHighlighted = false;
if (highlightedIndex === prev.length - 1) {
clonedPrev[0]!.isHighlighted = true;
} else {
clonedPrev[highlightedIndex + 1]!.isHighlighted = true;
}
return clonedPrev;
});
}
if (rawData === ARROW_UP) {
setItemsSelectionState((prev) => {
const highlightedIndex = prev.findIndex((item) => item.isHighlighted);
if (highlightedIndex === -1) {
return prev;
}
const clonedPrev = structuredClone(prev);
clonedPrev[highlightedIndex]!.isHighlighted = false;
if (highlightedIndex === 0) {
clonedPrev[prev.length - 1]!.isHighlighted = true;
} else {
clonedPrev[highlightedIndex - 1]!.isHighlighted = true;
}
return clonedPrev;
});
}
if (rawData === SPACE) {
setItemsSelectionState((prev) => {
const highlightedIndex = prev.findIndex((item) => item.isHighlighted);
if (highlightedIndex === -1) {
return prev;
}
const clonedPrev = structuredClone(prev);
clonedPrev[highlightedIndex]!.selected = !prev[highlightedIndex]!.selected;
return clonedPrev;
});
}
if (rawData === ENTER) {
const selectedItemsValues = itemsSelectionState
.filter((item) => item.selected)
.map((item) => item.value);
props.onSubmit(selectedItemsValues);
}
};
useEffect(() => {
setRawMode(true);
stdin?.on('data', stdinInputHandler);
return () => {
stdin?.removeListener('data', stdinInputHandler);
setRawMode(false);
};
}, []);
return <MultiSelectView itemsSelection={itemsSelectionState} />;
};
export default MultiSelect;
MultiSelect.view.tsx file:
import { Box, Text } from 'ink';
import React from 'react';
import type { ISelectItemSelection } from './interfaces/select-item';
interface IProps {
readonly itemsSelection: ISelectItemSelection[];
}
const MultiSelectView: React.FC<IProps> = (props: React.PropsWithChildren<IProps>) => {
return (
<Box display="flex" flexDirection="column">
{props.itemsSelection.map((item) => (
<Box key={item.value} display="flex" flexDirection="row">
<Text color="blue">{item.isHighlighted ? '❯' : ' '}</Text>
<Text color="magenta">
{item.selected ? '◉' : '◯'}
</Text>
<Text color="white" bold={item.selected}>
{item.label}
</Text>
</Box>
))}
</Box>
);
};
export default MultiSelectView;
Then, when I use this component in my code:
const onSubmitItems = (items: string[]) => {
console.log(items);
};
render(<MultiSelect items={items} onSubmit={onSubmitItems} />);
the render function is imported from ink and items is something like [{value: 'x', label: 'x'}, {value:'y', label:'y'}]
When I hit the enter key, Then onSubmitItems is triggered, but it outputs empty list, although I did select some items...
For example, in this output:
I picked 3 items, but output is still empty list. And it does seem like the state changes, so why I don't get the updated state?
You can resolve the issue by using useCallback:
const stdinInputHandler = useCallback((data: Buffer) => {
const rawData = String(data);
if (rawData === ARROW_DOWN) {
setItemsSelectionState((prev) => {
const highlightedIndex = prev.findIndex((item) => item.isHighlighted);
if (highlightedIndex === -1) {
return prev;
}
const clonedPrev = structuredClone(prev);
clonedPrev[highlightedIndex]!.isHighlighted = false;
if (highlightedIndex === prev.length - 1) {
clonedPrev[0]!.isHighlighted = true;
} else {
clonedPrev[highlightedIndex + 1]!.isHighlighted = true;
}
return clonedPrev;
});
}
if (rawData === ARROW_UP) {
setItemsSelectionState((prev) => {
const highlightedIndex = prev.findIndex((item) => item.isHighlighted);
if (highlightedIndex === -1) {
return prev;
}
const clonedPrev = structuredClone(prev);
clonedPrev[highlightedIndex]!.isHighlighted = false;
if (highlightedIndex === 0) {
clonedPrev[prev.length - 1]!.isHighlighted = true;
} else {
clonedPrev[highlightedIndex - 1]!.isHighlighted = true;
}
return clonedPrev;
});
}
if (rawData === SPACE) {
setItemsSelectionState((prev) => {
const highlightedIndex = prev.findIndex((item) => item.isHighlighted);
if (highlightedIndex === -1) {
return prev;
}
const clonedPrev = structuredClone(prev);
clonedPrev[highlightedIndex]!.selected = !prev[highlightedIndex]!.selected;
return clonedPrev;
});
}
if (rawData === ENTER) {
const selectedItemsValues = itemsSelectionState
.filter((item) => item.selected)
.map((item) => item.value);
props.onSubmit(selectedItemsValues);
}
}, [itemsSelectionState]);
Then, in your useEffect:
useEffect(() => {
setRawMode(true);
stdin?.on('data', stdinInputHandler);
return () => {
stdin?.removeListener('data', stdinInputHandler);
setRawMode(false);
};
}, [stdinInputHandler]);
now useEffect will re-register the input handler with new functions locals up-to-date with the state
I am making a simple e-commerce website but I've ran into an issue where useEffect() won't fire after making a state change. This code snippet I'll include is for the "shopping cart" of the website and uses localStorage to store all items in the cart. My state will change when quantity changes in the QuantChange() function but will not trigger useEffect(). When I refresh the page after changing an item's quantity, the new quantity won't persist and the old quantity is shown instead. What am I doing wrong? Thanks in advance.
import React, { useState, useEffect } from 'react';
import { SetQuantity } from '../utils/Variables';
import { CartItem } from './CartItem';
const CartView = () => {
const [state, setState] = useState(
JSON.parse(localStorage.getItem('cart-items'))
? JSON.parse(localStorage.getItem('cart-items'))
: []
);
useEffect(() => {
console.log('Updating!');
updateLocalStorage();
});
const updateLocalStorage = () => {
localStorage.setItem('cart-items', JSON.stringify(state));
};
const quantChange = (event) => {
setState((prevState) => {
prevState.forEach((item, index) => {
if (item._id === event.target.id) {
item.quantity = SetQuantity(parseInt(event.target.value), 0);
prevState[index] = item;
}
});
return prevState;
});
};
const removeItem = (id) => {
setState((prevState) => prevState.filter((item) => item._id != id));
};
// Fragments need keys too when they are nested.
return (
<>
{state.length > 0 ? (
state.map((item) => (
<CartItem
key={item._id}
ID={item._id}
name={item.name}
quantity={item.quantity}
changeQuant={quantChange}
delete={removeItem}
/>
))
) : (
<h1 className="text-center">Cart is Empty</h1>
)}
</>
);
};
export default CartView;
import React, { Fragment } from 'react';
import { MAX_QUANTITY, MIN_QUANTITY } from '../utils/Variables';
export const CartItem = (props) => {
return (
<>
<h1>{props.name}</h1>
<input
id={props.ID}
type="number"
max={MAX_QUANTITY}
min={MIN_QUANTITY}
defaultValue={props.quantity}
onChange={props.changeQuant}
/>
<button onClick={() => props.delete(props.ID)} value="Remove">
Remove
</button>
</>
);
};
export const MIN_QUANTITY = 1;
export const MAX_QUANTITY = 99;
// Makes sure the quantity is between MIN and MAX
export function SetQuantity(currQuant, Increment) {
if (Increment >= 0) {
if (currQuant >= MAX_QUANTITY || (currQuant + Increment) > MAX_QUANTITY) {
return MAX_QUANTITY;
} else {
return currQuant + Increment;
}
} else {
if (currQuant <= MIN_QUANTITY || (currQuant + Increment) < MIN_QUANTITY) {
return MIN_QUANTITY;
} else {
return currQuant + Increment;
}
}
}
You are not returning new state, you are forEach'ing over it and mutating the existing state and returning the current state. Map the previous state to the next state, and for the matching item by id create and return a new item object reference.
const quantChange = (event) => {
const { id, value } = event.target;
setState((prevState) => {
return prevState.map((item) => {
if (item._id === id) {
return {
...item,
quantity: SetQuantity(parseInt(value), 0)
};
}
return item;
});
});
};
Then for any useEffect hook callbacks you want triggered by this updated state need to have the state as a dependency.
useEffect(() => {
console.log('Updating!');
updateLocalStorage();
}, [state]);
I am working on solution
I have created basic tree kind of table whenever user click on expand data related to clicked row will appear under it based on row data
I have achieved basic functionality of expand/collapse upto N nested levels.
But i am stuck with only one problem, so basically all row have conditional expand button based on array having multiple values
Lets say it is split array having 3 entries county,city,state
Default loaded data will be fetched from api, now i have to check array that is there any split available! if yes than i make expand button visible
Consider this scenario
const split = ["country","city","state"]
this is Ui will look like
+ Data_1
+ Data_2
on click of button + new data table row will be rendered based on next split available in our case it is country so visual representation will be like
- Data_1
Country_1
Country_2
+ Data_2
Here country does not have expand button as user have not added next split yet, lets add city, and assume user have clicked Country_1 so data will be like
- Data_1
- Country_1
City_1
City_2
+ Country_2
+ Data_2
My solution works fine till this level now lets say user have removed country from split that all nodes of country and city should be removed and - icon of data_1 should be changed to +
Here is my code
import React, {useState, useEffect, useRef, Fragment} from "react";
import _ from "lodash";
import axios from "axios";
class TableRowData extends React.Component {
state = {
showIcon: false,
selection: [],
data: [],
splitOption: ["campid"]
};
constructor(props) {
super(props);
}
componentDidMount() {
const checkIfSplitExistOnMount = (currentSplit) => {
const i = _.findIndex(this.state.splitOption, function(el) {
return el === currentSplit;
});
if (this.state.splitOption[i + 1]) {
return this.state.splitOption[i + 1];
} else {
return null;
}
}
const getReportData = () => {
axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then((res) => {
const rowData = res.data.map((row) => {
row.name = this.state.splitOption[0];
row.isExpanded = false;
row.currentSplit = this.state.splitOption[0];
row.nextSplit = checkIfSplitExistOnMount(this.state.splitOption[0])
row.parentId = 0;
row.isVisble = true;
//console.log(row)
return row;
});
this.setState({
data: rowData
}, () => { //console.log(this.state.data)
});
});
}
getReportData()
}
render() {
// update state function
const updateState = () => {
this.setState({
data: [...this.state.data],
splitOption: [...this.state.splitOption],
selection: [...this.state.selection],
}, () => {})
}
// recusively update parent and child
const recursion = (obj) => {
let row = obj;
row.isExpanded = row.isExpanded;
row.currentSplit = row.currentSplit;
row.nextSplit = checkIfSplitExist(row.currentSplit)
if (row.children && row.children.length > 0) { // check if has children
row.children.forEach(v => { // if has children do the same recursion for every children
recursion(v);
});
}
return row; // return final new object
}
const recursionDel = (obj,split) => {
var row = obj;
row.currentSplit = row.currentSplit;
row.nextSplit = checkIfSplitExist(row.currentSplit)
if (row.children && row.children.length > 0) { // check if has children
row.children.forEach(v => { // if has children do the same recursion for every children
recursionDel(v);
});
}
return row; // return final new object
}
// function to check if next split is there or not if there than return nextsplit
const checkIfSplitExist = (currentSplit) => {
const i = _.findIndex(this.state.splitOption, function(el) {
return el === currentSplit;
});
if(i !== -1) {
if (this.state.splitOption[i + 1]) {
return this.state.splitOption[i + 1];
} else {
return null;
}
}
}
// recursive update whenever split added
const recursiveUpdate = (data) => {
const prevData = [...data];
return prevData.map((row) => {
const updatedData = recursion(row);
return row;
});
}
// function to delete child and parent node recursively
const recursiveDelete = (data,split) => {
const prevData = [...data];
return prevData.map((row) => {
const data = recursionDel(row,split);
return row;
});
}
const addNewSplit = (split) => {
const i = _.findIndex(this.state.splitOption, function(el) {
return el === split;
});
if(i === -1) {
this.setState(
{
splitOption:[...this.state.splitOption,split]
},
()=>{
var rowData = recursiveUpdate(this.state.data)
this.setState({data:rowData})
}
);
} else {
const prevData = [...this.state.splitOption];
var index = prevData.indexOf(split);
prevData.splice(index,1)
if(index!==-1) {
this.setState(
{
splitOption:prevData
},
()=> {
var rowData = recursiveDelete(this.state.data,split)
this.setState({data:rowData})
}
)
}
}
}
// add lazyload expand data
const ExpandableTableRow = ({rows}) => {
const expandRow = (row) => {
row.children = [
{
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: checkIfSplitExist(row.nextSplit),
isVisble:true
}, {
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: checkIfSplitExist(row.nextSplit),
isVisble:true
}
];
row.isExpanded = true;
updateState();
};
// call whenever - click
const collapseRow = (row) => {
delete row.children;
row.isExpanded = false;
updateState();
};
// toggle
const ExpandCollapsToggle = ({row, expandRow, collapseRow}) => {
// display +/- only if nextsplit is not undefined or null
if (row.nextSplit) {
if (row.isExpanded === true) {
return (<button type="button" onClick={() => collapseRow(row)}>
-
</button>);
} else {
return (<button type="button" onClick={() => expandRow(row)}>
+
</button>);
}
} else {
return null;
}
};
if (rows) {
return rows.map((row) => {
// if(!_.isEmpty(row)) {
return (<Fragment key={row.id}>
<tr key={row.id}>
<td>
<ExpandCollapsToggle row={row} expandRow={expandRow} collapseRow={collapseRow}/>{" "}
{row.split}
- {row.id}
</td>
<td>{row.name}</td>
</tr>
<ExpandableTableRow rows={row.children}/>
</Fragment>);
// }
});
} else {
return null;
}
};
const splitData = this.state.splitOption.map((ob) => {
return (<Fragment key={ob}><span>{ob}</span> > </Fragment>)
})
if (this.state.data) {
return (
<Fragment>
{splitData} <br/>
<button onClick = {()=>addNewSplit("name")}>camp name</button>
<button onClick = {()=>addNewSplit("os")}>os</button>
<button onClick = {()=>addNewSplit("country")}>country</button>
<ExpandableTableRow rows={this.state.data} />
</Fragment>
);
} else {
return null;
}
}
}
export default TableRowData;
Also i have create example of codesandbox.io - Link
Here is how you play with ui to replicate scenario
First click on camp name, expand icon will appear
Now expand if you want to, you can see data according split under
Now add one more split OS or Country and you can see expand icon with 2nd level rows
Next step is to remove "Camp Name", Here is issue when camp name is removed, table should be re render according available splits, in our case user's all row should be removed and + icon must be there are we have next split os or country available, i used default split id, it will be there always
import React, { useState, useEffect, useRef, Fragment } from "react";
import axios from "axios";
const test_data = [{
"id":1,
"name":"Leanne Graham",
"username":"Bret",
"email":"Sincere#april.biz",
"address":{
"street":"Kulas Light",
"suite":"Apt. 556",
"city":"Gwenborough",
"zipcode":"92998-3874",
"geo":{
"lat":"-37.3159",
"lng":"81.1496"
}
},
"phone":"1-770-736-8031 x56442",
"website":"hildegard.org",
"company":{
"name":"Romaguera-Crona",
"catchPhrase":"Multi-layered client-server neural-net",
"bs":"harness real-time e-markets"
}
}];
class TableRowData extends React.Component {
constructor(props) {
super(props);
this.state = {
showIcon: false,
selection: [],
data: [],
splitOption: ["campid"]
};
}
// function to check if next split is there or not if there than return nextsplit
checkIfSplitExist = (currentSplit) => {
const i = this.state.splitOption.indexOf(currentSplit);
if (i > -1 && this.state.splitOption[i + 1]) {
return this.state.splitOption[i + 1];
}
return null;
}
getReportData = () => {
// axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then(({data}) => {
this.setState({
data: test_data.map((row) => {
row.name = this.state.splitOption[0];
row.isExpanded = false;
row.currentSplit = this.state.splitOption[0];
row.nextSplit = this.checkIfSplitExist(this.state.splitOption[0])
row.parentId = 0;
row.isVisble = true;
console.log(row)
return row;
})
});
// });
}
componentDidMount() {
this.getReportData()
}
render() {
// update state function
const updateState = () => {
this.setState({
data: [...this.state.data],
splitOption: [...this.state.splitOption],
selection: [...this.state.selection],
}, () => { })
}
const recursionUpdateAndDeleteRow = (parentRow, childRow, split, index = 0) => {
childRow.children && childRow.children.forEach((r) => {
recursionUpdateAndDeleteRow(childRow, r, split, index + 1);
});
if (parentRow && split.indexOf(childRow.currentSplit) == -1) {
delete parentRow.children;
}
childRow.currentSplit = split[index];
childRow.nextSplit = split[index + 1] || null;
if (!childRow.children) {
childRow.isExpanded = false;
}
}
const recursionUpdateAndDeleteRows = (rows, split) => {
const _copy = [...rows];
_copy.forEach((row) => {
recursionUpdateAndDeleteRow(null, row, split);
});
return _copy;
}
const toggleSplit = (split) => {
const index = this.state.splitOption.indexOf(split);
let currentSplitOptions = [...this.state.splitOption];
if (index > -1) {
currentSplitOptions.splice(index, 1)
}
else {
currentSplitOptions.push(split);
}
const _data = recursionUpdateAndDeleteRows(this.state.data, currentSplitOptions);
this.setState({
splitOption: currentSplitOptions,
data: _data
})
}
// add lazyload expand data
const ExpandableTableRow = ({ rows }) => {
const expandRow = (row) => {
row.children = [
{
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: this.checkIfSplitExist(row.nextSplit),
isVisble: true
}, {
id: "_" + Math.random().toString(36).substr(2, 5),
name: row.id + "_" + row.nextSplit,
isExpanded: false,
parentId: row.id,
currentSplit: row.nextSplit,
nextSplit: this.checkIfSplitExist(row.nextSplit),
isVisble: true
}
];
row.isExpanded = true;
updateState();
};
// call whenever - click
const collapseRow = (row) => {
delete row.children;
row.isExpanded = false;
updateState();
};
// toggle
const ExpandCollapsToggle = ({ row }) => {
// display +/- only if nextsplit is not undefined or null
if (row.nextSplit) {
if (row.isExpanded) {
return (
<button type="button" onClick={() => collapseRow(row)}>
-
</button>
);
}
return (
<button type="button" onClick={() => expandRow(row)}>
+
</button>
);
}
return null;
};
if (rows) {
return rows.map((row) => {
return (
<Fragment key={row.id}>
<tr key={row.id}>
<td>
<ExpandCollapsToggle
row={row}
/>
{" "}{row.split} - {row.id}
</td>
<td>{row.name}</td>
</tr>
<ExpandableTableRow rows={row.children} />
</Fragment>
);
});
} else {
return null;
}
};
if (this.state.data) {
return (
<Fragment>
{this.state.splitOption.join(', ')} <br />
<button onClick={() => toggleSplit("name")}>
camp name
</button>
<button onClick={() => toggleSplit("os")}>os</button>
<button onClick={() => toggleSplit("country")}>country</button>
<br />
<ExpandableTableRow rows={this.state.data} />
</Fragment>
);
} else {
return null;
}
}
}
export default function App() {
return (
<div>
<TableRowData />
</div>
);
}
Here working example
I'm building a e-commerce store where I have the following data structure:
productsData.js
export const storeProducts = [
{
id: 1,
title: "Santos",
img: "img/pfs - black.png",
price: 59.70,
description: "Lorem Ipsum",
gender: ["Feminine", "Masculine"],
info:
"Lorem Ipsum",
inCart: false,
count: 0,
total: 0
}
];
export const detailProducts = {
id: 1,
title: "Pecadores Feitos Santos",
img: "img/pfs - black.png",
price: 59.70,
description: "Lorem ipsum",
gender: ["Feminine", "Masculine"],
info:
"Lorem ipsum.",
inCart: false,
count: 0,
total: 0
};
I have a Context React file that is my provider and consumer:
context.js
import React, { Component } from 'react'
import { storeProducts, detailProducts } from './productsData';
const ProductContext = React.createContext();
// Provider and Consumer
class ProductProvider extends Component {
state = {
products: [],
detailProducts: detailProducts,
cart: [],
modalOpen: false,
modalProduct: detailProducts,
cartTotal: 0
};
componentDidMount() {
this.setProducts();
}
setProducts = () => {
let tempProducts = [];
storeProducts.forEach(item => {
const singleItem = {...item};
tempProducts = [...tempProducts, singleItem];
});
this.setState(() => {
return { products: tempProducts };
// console.log("State products: ", storeProducts[0].color[0])
});
};
getItem = id => {
const product = this.state.products.find(item => item.id === id);
return product;
};
handleDetail = id => {
const product = this.getItem(id);
this.setState(() => {
return { detailProducts: product };
});
};
addToCart = id => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
// console.log(product.gender);
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return { products: tempProducts, cart: [...this.state.cart,product] }
}, () => { this.addTotal();});
}
openModal = id => {
const product = this.getItem(id);
this.setState(() => {
return {modalProduct: product, modalOpen: true}
})
}
closeModal = () => {
this.setState(() => {
return {modalOpen: false}
})
}
increment = (id) => {
let tempCart = [...this.state.cart];
const selectedProduct = tempCart.find(item => item.id === id);
const index = tempCart.indexOf(selectedProduct);
const product = tempCart[index];
product.count = product.count + 1;
product.total = product.count * product.price;
this.setState(() => {
return {
cart: [...tempCart]
}
}, () => { this.addTotal() })
}
decrement = (id) => {
let tempCart = [...this.state.cart];
const selectedProduct = tempCart.find(item => item.id === id);
const index = tempCart.indexOf(selectedProduct);
const product = tempCart[index];
product.count = product.count - 1;
if(product.count === 0) {
this.removeItem(id);
}
else {
product.total = product.count * product.price;
this.setState(() => {
return {
cart: [...tempCart]
}
}, () => { this.addTotal() })
}
}
removeItem = (id) => {
let tempProducts = [...this.state.products];
let tempCart = [...this.state.cart];
tempCart = tempCart.filter(item => item.id !== id);
const index = tempProducts.indexOf(this.getItem(id));
let removedProduct = tempProducts[index];
removedProduct.inCart = false;
removedProduct.count = 0;
removedProduct.total = 0;
this.setState(() => {
return {
cart:[...tempCart],
products: [...tempProducts]
}
}, ()=> {
this.addTotal();
})
}
clearCart = () => {
this.setState(() => {
return { cart: [] };
}, () => {
this.setProducts();
this.addTotal();
})
}
addTotal = () => {
let total = 0;
this.state.cart.map(item => (total += item.total));
this.setState(() => {
return {
cartTotal: total
}
})
}
render() {
return (
<ProductContext.Provider value={{
...this.state,
handleDetail: this.handleDetail,
addToCart: this.addToCart,
openModal: this.openModal,
closeModal: this.closeModal,
increment: this.increment,
decrement: this.decrement,
removeItem: this.removeItem,
clearCart: this.clearCart
}}
>
{this.props.children}
</ProductContext.Provider>
)
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
Everything works just fine, but here's where the problems start:
Details.js
import React, { Component } from 'react';
import { ProductConsumer } from '../context';
import { Link } from 'react-router-dom';
import { ButtonContainerSecondary } from './ButtonSecondary';
import { ButtonDetails } from './ButtonDetails';
import { ColorButton } from './ColorButton';
import PropTypes from 'prop-types';
export default class Details extends Component {
render() {
return (
<ProductConsumer>
{value => {
const {id, img, price, description, color, gender, info, title, inCart} = value.detailProducts;
return (
<div className="container pb-5">
<div className="row">
<div className="col-10 mx-auto text-center text-slanted my-5">
{gender.map((item, key) => (
<span>{" "}<ButtonDetails key={key} onClick={() => { this.setState({gender: key}); console.log(key)}}
</div>
</div>
</div>
)
}}
</ProductConsumer>
)
}
}
Details.propTypes = {
product: PropTypes.shape({
color: PropTypes.arrayOf(PropTypes.string),
gender: PropTypes.arrayOf(PropTypes.string)
})
}
I can't seem to figure out how to pass in the state in the nested array gender to the next level. I do get the console.log(key) right, but it gets lost as I move up to the cart. I would like to give the user the chance to choose from a feminine or masculine shirt:
CartItem.js
import React from 'react'
export default function CartItem({ item, value }) {
const {id, title, img, price, gender, total, count} = item;
const {increment, decrement, removeItem} = value;
return (
<div className="col-10 mx-auto col-lg-2">
<span className="d-lg-none">Product: </span> {title} {gender} {console.log(gender)}
</div>
);
}
Here the console.log(gender) returns both items of the array altogether ("FeminineMasculine").
I'm a newbie and would really appreciate your help. Sorry if anything!
I built an E-commerce site for pizza. It could be viewed here: https://chinomso1995.github.io/dodosPizza/.
I have created an orders page and On the orders page when you try to increment items, it increments in two's. I have gone through the reducer code I wrote and the context code and I seem to have written everything correctly. I can't pinpoint where the problem is coming from.
JSX code for my reducer
const Storage = (cartItems) => {
localStorage.setItem('cart', JSON.stringify(cartItems.length > 0 ? cartItems: []));
}
export const sumItems = cartItems => {
Storage(cartItems);
let itemCount = cartItems.reduce((total, product) => total + product.quantity, 0);
let total = cartItems.reduce((total, product) => total + product.price * product.quantity, 0).toFixed(2);
return { itemCount, total }
}
export const CartReducer = (state, action) => {
switch (action.type) {
case "ADD_ITEM":
if (!state.cartItems.find(item => item.id === action.payload.id)) {
state.cartItems.push({
...action.payload,
quantity: 1
})
}
return {
...state,
...sumItems(state.cartItems),
cartItems: [...state.cartItems]
}
case "REMOVE_ITEM":
return {
...state,
...sumItems(state.cartItems.filter(item => item.id !== action.payload.id)),
cartItems: [...state.cartItems.filter(item => item.id !== action.payload.id)]
}
case "INCREASE":
state.cartItems[state.cartItems.findIndex(item => item.id === action.payload.id)].quantity++
return {
...state,
...sumItems(state.cartItems),
cartItems: [...state.cartItems]
}
case "DECREASE":
state.cartItems[state.cartItems.findIndex(item => item.id === action.payload.id)].quantity--
return {
...state,
...sumItems(state.cartItems),
cartItems: [...state.cartItems]
}
case "CHECKOUT":
return {
cartItems: [],
checkout: true,
...sumItems([]),
}
case "CLEAR":
return {
cartItems: [],
...sumItems([]),
}
default:
return state
}
}
JSX code for the context
import React, { createContext, useReducer, use } from 'react';
import { CartReducer, sumItems } from './OrderReducer';
export const CartContext = createContext()
const storage = localStorage.getItem('cart') ? JSON.parse(localStorage.getItem('cart')) : [];
const initialState = { cartItems: storage, ...sumItems(storage), checkout: false };
const CartContextProvider = ({children}) => {
const [state, dispatch] = useReducer(CartReducer, initialState)
const increase = payload => {
dispatch({type: 'INCREASE', payload})
}
const decrease = payload => {
dispatch({type: 'DECREASE', payload})
}
const addProduct = payload => {
dispatch({type: 'ADD_ITEM', payload})
}
const removeProduct = payload => {
dispatch({type: 'REMOVE_ITEM', payload})
}
const clearCart = () => {
dispatch({type: 'CLEAR'})
}
const handleCheckout = () => {
console.log('CHECKOUT', state);
dispatch({type: 'CHECKOUT'})
}
const contextValues = {
removeProduct,
addProduct,
increase,
decrease,
clearCart,
handleCheckout,
...state
}
return (
<CartContext.Provider value={contextValues} >
{ children }
</CartContext.Provider>
);
}
export default CartContextProvider;
JSX code for the Product Context
export const ProductsContext = createContext()
const ProductsContextProvider = ({children}) => {
const [pizzaproducts] = useState(Pizza)
const [sideproducts] = useState(Sides);
const [dessertproducts] = useState(Desserts)
const [drinkproducts] = useState(Drinks)
return (
<ProductsContext.Provider value={{sideproducts, dessertproducts, drinkproducts, pizzaproducts}} >
{ children }
</ProductsContext.Provider>
);
}
export default ProductsContextProvider;
JSX code for a section
const Sides = ()=> {
const {sideproducts} = useContext(ProductsContext);
const {addProduct, cartItems, increase} = useContext(CartContext);
const isInCart = sideproducts => {
return !!cartItems.find(item => item.id === sideproducts.id);
}
return(
<div className={styles.Sides} id='sides'>
<h1>Sides</h1>
<div className={styles.SidesContainer}>
{sideproducts.map(side=>{
return <div className={styles.SidesCard}>
<div className={styles.ImageContainer}>
<img src={side.image} alt="sausagerollone"/>
</div>
<div className={styles.SidesHeader}>
<div>
<h1>{side.name}</h1>
<p>{side.details}</p>
</div>
<div className={styles.SidesFooter}>
<h3>₦{side.price}</h3>
{
isInCart(side) &&
<button
onClick={() => increase(side)}>Add more</button>
}
{ !isInCart(side) &&
<button onClick={()=>addProduct(side)}>
<span>from ₦{side.price}</span>
<span>Add to basket</span>
</button>}
</div>
</div>
</div> })}
</div>
</div>
)
}
export default Sides;
In the increased case of the reducer code, I increment each item by one but it does increment by two in the app itself. This also affects the total price.