How to remove items from the Cart? - javascript

I'm new to the JavaScript and I face a problem. I don't understand how to create an event which will remove items from the Cart page. I created addCartEvent but I don't know how to remove products. I already created minus button component. In project I'm using Firebase.
Here's a code from the Cart page:
export default function CartPage() {
const { user, loading } = useAuth();
const { data } = useCart();
const cartLength = Object.keys(data).reduce((a, b) => a + data[b].length, 0);
const cartItems =
cartLength > 0
? Object.keys(data)
.map((item) => {
return data[item].map((size) => {
return {
name: item,
size,
};
});
})
.flat(1)
: [];
const sizeCount = cartItems.reduce(
(acc, value) => ({
...acc,
[value.name + '__size__' + value.size]:
(acc[value.name + '__size__' + value.size] || 0) + 1,
}),
{}
);
const cartItemsArray = [
...new Set(
cartItems.filter(
(v, i, a) =>
a.findIndex((t) => t.name === v.name && t.size === v.size) === i
)
),
].map((item) => {
return { ...item, count: sizeCount[item.name + '__size__' + item.size] };
});
const addCartEvent = (id, size) => {
const newCart = size
? {
...data,
[id]: data.hasOwnProperty(id) ? [...data[id], size] : [size],
}
: {
...data,
[id]: data.hasOwnProperty(id) ? [...data[id], '-'] : ['-'],
};
addToCart(newCart);
};
const router = useRouter();
}
And code from the firebase/product.js:
import { auth, db } from '../config/firebase';
function addToCart(newCart) {
const currentUser = auth.currentUser.uid;
return db.collection('Users').doc(currentUser).update({
cart: newCart,
});
}
export { addToCart };

Related

Vue 3 search component: Maximum call stack size exceeded

I have like this component for searching in my app:
<template>
<div class="search">
<input
type="text"
#input="search($event.target.value.trim())"
/>
</div>
</template>
<script setup>
const props = defineProps({
data: Array,
metro: Object,
})
const emit = defineEmits(['onSearch'])
const metro = ref({})
const query = ref("")
const filterByMetro = () => {
if (metro.value.id) {
var data = props.data
if(query.value) {
data = search(query.value)
}
const result = data.filter(item => metro.value.metro == 1 ? item.district.id == metro.value.id : item.district.isMetro == false)
emit('onSearch', result)
return result
}
}
const search = (val) => {
var data = props.data
if(metro.value.id) {
data = filterByMetro()
}
if (val !== '' && data && data.length > 0) {
const rx = new RegExp("^" + val, "im")
const rxm = new RegExp('(?<= )[а-яА-Яa-zA-Z][а-яА-Яa-zA-Z ]*(?=,| )', 'giu')
const result = data.map(item => [(rx.test(item.address.match(rxm)?.[0] || null) ? 1 : 0) ||
(rx.test(item.district.map(d => d.title).join("\n")) ? 2 : 0), item
])
.filter(([k, v]) => k)
.sort((a, b) => a[0] - b[0])
.map(([k, v]) => v);
emit('onSearch', result, val)
query.value = val
return result
} else {
emit('onSearch', data)
return data
}
}
watch(() => props.metro, (val, old) => {
metro.value = val
if(val.id) {
// Filter by metro
filterByMetro()
} else {
// Reset metro
search(query.value)
}
});
</script>
I use component like this:
<Searchbar
:data="list"
#onSearch="setResult"
:metro="currentMetro" // ref
/>
How I can change logic here to avoid Maximum call stack size exceeded error in my case?

Javascript: array as useEffect depencendy

useEffect(() => {
changeColumnsState(mapOrder({array: columnsState, order: columnOrder, key: 'name'}))
}, [JSON.stringify(columnOrder)])
What is the best way to check if array changed? Not length, but order
columnOrder is array of numbers.
export const ColumnsCustomizeModalForm: FunctionComponent<
ColumnsCustomizeModalFormProps
> = ({ columns, onClose, modalProps }) => {
const columnInitial = columns.map((column) => column.name)
const [columnOrder, setColumnOrder] =
useState<string[]>(columnInitial)
const [columnsState, changeColumnsState] = useState(mapOrder({array: columns, order: columnOrder, key: 'name'}))
useEffect(() => {
changeColumnsState(mapOrder({array: columnsState, order: columnOrder, key: 'name'}))
}, [JSON.stringify(columnOrder)])
const setColumnVisibility = (name: string) => {
const newColumnsState = columnsState.map((column) => {
if(column.name === name) {
return {...column, isHidden: !column.isHidden}
}
return column
})
changeColumnsState(newColumnsState)
}
return (
<ColumnsCustomizationModalView
columns={columnsState}
handleChangeOrder={setColumnOrder}
handleColumnVisibility={setColumnVisibility}
handleClose={onClose}
modalProps={modalProps}
/>
)
}
export const mapOrder = <ObjectType, KeyType extends keyof ObjectType>({array, order, key}: {array: ObjectType[], order: ObjectType[KeyType][], key: KeyType}) => {
const sortedArray = array.sort( function (a, b) {
var A = a[key], B = b[key];
if (order.indexOf(A) > order.indexOf(B)) {
return 1;
} else {
return -1;
}
});
return sortedArray;
};
What is the best way to check if array changed?
The best way is not to check at all. You don't need that columnOrder array state and an initialisation and an effect depending on it. Just directly set the order of your columnsState array in the handler function:
export const ColumnsCustomizeModalForm: FunctionComponent<
ColumnsCustomizeModalFormProps
> = ({ columns, onClose, modalProps }) => {
const [columnsState, changeColumnsState] = useState(columns);
const setColumnOrder = (columnOrder: string[]) => {
changeColumnsState(array => mapOrder({array, order: columnOrder, key: 'name'}))
};
const setColumnVisibility = (name: string) => {
changeColumnsState(cols => cols.map(column => {
if (column.name === name) {
return {...column, isHidden: !column.isHidden}
}
return column
}));
};
return (
<ColumnsCustomizationModalView
columns={columnsState}
handleChangeOrder={setColumnOrder}
handleColumnVisibility={setColumnVisibility}
handleClose={onClose}
modalProps={modalProps}
/>
);
}
Also fix your mapOrder function to create a new array (and use correct comparison):
export function mapOrder<ObjectType, KeyType extends keyof ObjectType>({array, order, key}: {array: ObjectType[], order: ObjectType[KeyType][], key: KeyType}) {
return array.slice().sort((a, b) =>
// ^^^^^^^^
order.indexOf(a[key]) - order.indexOf(b[key])
);
}

How to update a state variable correctly using onClick in functional component

I have created two tabs that when clicked need to show a different set of products and a different set of filters for each selection. My problem is that when I click either tab and call setOptions within changeTab, I need to click each tab twice before it will update 'options', 'options' needs to contain each filter.
Obviously calling setOptions within the click handler is not correct but I can't figure out where or how to correctly update 'options'. Help greatly appreciated.
In the console logs below 'dedupedOptions' updates correctly on click
function filterProducts() {
const [categoryType, setCategory] = useState("Canine");
const [activeTabIndex, setActiveTabIndex] = useState(0);
const {
productData: {
products: { products }
}
} = useContext(AppContext);
const productsByCategory = products
.filter((product) => {
const { tags } = product;
return !!tags.find((tag) => tag.includes(categoryType));
})
.map((product) => ({
...product,
category: product.tags
.find((tag) => tag.includes("category:"))
.split(":")[1]
}));
let dedupedOptions = [];
productsByCategory.forEach((product) => {
const { tags } = product;
tags.forEach((tag) => {
const parts = tag.split(":");
const key = parts[0];
const value = parts[1] || null;
const validTag = tagKeysToDisplay.find(
(tagKeyToDisplay) => tagKeyToDisplay === key
);
if (
validTag &&
!dedupedOptions.find((dedupedOption) => dedupedOption.value === value)
) {
dedupedOptions = [
...dedupedOptions,
{
label: titleCase(value),
value,
selected: false
}
];
}
});
});
const [options, setOptions] = useState(dedupedOptions);
console.log(dedupedOptions);
console.log(options);
const changeTab = (index, category) => {
setCategory(category);
setActiveTabIndex(index);
setOptions(dedupedOptions);
};
const setFilter = useCallback(
(selectedOption) => {
const optionIsActive = options.find(
(option) => option.value === selectedOption.value
)?.selected;
let newOptions = [];
newOptions = [
...options.map((option) => {
if (option.value === selectedOption.value) {
return {
...option,
selected: !optionIsActive
};
}
return option;
})
];
setOptions(newOptions);
},
[options]
);
}
And the two elements set up as tabs to handle the click events. These are rendered within the same filterProducts function.
<div className="filter-products__tabs">
<div
className={`filter-products__tab
${activeTabIndex === 0 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 0, "Canine")}
>
<span>DOG</span>
</div>
<div
className={`filter-products__tab
${activeTabIndex === 1 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 1, "Feline")}
>
<span>CAT</span>
</div>
</div>
I reproduced your question by some changes in variable declarations in state.
be careful to declare variables in state and do the updates by listening the variable changes inside the useEffect.
here is the working code:\
https://codesandbox.io/s/quirky-http-e264i?file=/src/App.js
import "./styles.css";
import { useState, useContext, useCallback, useEffect } from "react";
export default function App() {
const [categoryType, setCategory] = useState("Canine");
const [activeTabIndex, setActiveTabIndex] = useState(0);
const [productsByCategory, setProductsByCategory] = useState([]);
const [dedupedOptions, setDedupedOptions] = useState([]);
const [options, setOptions] = useState(dedupedOptions);
const products = [
{ tags: ["category:Feline"], name: "one" },
{ tags: ["category:Canine"], name: "two" }
];
useEffect(() => {
const productsByCategory = products
.filter((product) => {
const { tags } = product;
return !!tags.find((tag) => tag.includes(categoryType));
})
.map((product) => ({
...product,
category: product.tags
.find((tag) => tag.includes("category:"))
.split(":")[1]
}));
setProductsByCategory(productsByCategory);
}, [categoryType]);
useEffect(() => {
let tmp_dedupedOptions = [];
const tagKeysToDisplay = ["category"];
productsByCategory.forEach((product) => {
const { tags } = product;
tags.forEach((tag) => {
const parts = tag.split(":");
const key = parts[0];
const value = parts[1] || null;
const validTag = tagKeysToDisplay.find(
(tagKeyToDisplay) => tagKeyToDisplay === key
);
if (
validTag &&
!tmp_dedupedOptions.find(
(dedupedOption) => dedupedOption.value === value
)
) {
tmp_dedupedOptions = [
...tmp_dedupedOptions,
{
label: value,
value,
selected: false
}
];
}
});
});
setDedupedOptions(tmp_dedupedOptions);
setOptions(tmp_dedupedOptions);
}, [productsByCategory]);
console.log("options: ", options);
const changeTab = (index, category) => {
setCategory(category);
setActiveTabIndex(index);
};
const setFilter = useCallback(
(selectedOption) => {
const optionIsActive = options.find(
(option) => option.value === selectedOption.value
)?.selected;
let newOptions = [];
newOptions = [
...options.map((option) => {
if (option.value === selectedOption.value) {
return {
...option,
selected: !optionIsActive
};
}
return option;
})
];
setOptions(newOptions);
},
[options]
);
// }
return (
<div>
<div className="filter-products__tabs">
<div
className={`filter-products__tab
${activeTabIndex === 0 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 0, "Canine")}
>
<span>DOG</span>
</div>
<div
className={`filter-products__tab
${activeTabIndex === 1 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 1, "Feline")}
>
<span>CAT</span>
</div>
</div>
</div>
);
}

how to add multiple objects in reactjs?

I want to add new Objects when user click on checkbox. For example , When user click on group , it will store data {permission:{group:["1","2"]}}. If I click on topgroup , it will store new objects with previous one
{permission:{group:["1","2"]},{topGroup:["1","2"]}}.
1st : The problem is that I can not merge new object with previous one . I saw only one objects each time when I click on the group or topgroup.
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
Object.assign(prevState.permission, { [value]: this.state.checked });
});
});
};
<CheckboxGroup
options={options}
value={checked}
onChange={this.onChange(this.props.label)}
/>
Here is my codesanbox:https://codesandbox.io/s/stackoverflow-a-60764570-3982562-v1-0qh67
It is a lot of code because I've added set and get to set and get state. Now you can store the path to the state in permissionsKey and topGroupKey. You can put get and set in a separate lib.js.
In this example Row is pretty much stateless and App holds it's state, this way App can do something with the values once the user is finished checking/unchecking what it needs.
const Checkbox = antd.Checkbox;
const CheckboxGroup = Checkbox.Group;
class Row extends React.Component {
isAllChecked = () => {
const { options, checked } = this.props;
return checked.length === options.length;
};
isIndeterminate = () => {
const { options, checked } = this.props;
return (
checked.length > 0 && checked.length < options.length
);
};
render() {
const {
options,
checked,
onChange,
onToggleAll,
stateKey,
label,
} = this.props; //all data and behaviour is passed by App
return (
<div>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={e =>
onToggleAll(e.target.checked, stateKey)
}
checked={this.isAllChecked()}
>
Check all {label}
</Checkbox>
<CheckboxGroup
options={options}
value={checked}
onChange={val => {
onChange(stateKey, val);
}}
/>
</div>
</div>
);
}
}
//helper from https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9
const REMOVE = () => REMOVE;
const get = (object, path, defaultValue) => {
const recur = (current, path) => {
if (current === undefined) {
return defaultValue;
}
if (path.length === 0) {
return current;
}
return recur(current[path[0]], path.slice(1));
};
return recur(object, path);
};
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path) => {
if (path.length === 1) {
return setKey(
current,
path[0],
callback(current[path[0]])
);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1))
);
};
return recur(object, path, callback);
};
class App extends React.Component {
state = {
permission: { group: [] },
topGroup: [],
some: { other: [{ nested: { state: [] } }] },
};
permissionsKey = ['permission', 'group']; //where to find permissions in state
topGroupKey = ['topGroup']; //where to find top group in state
someKey = ['some', 'other', 0, 'nested', 'state']; //where other group is in state
onChange = (key, value) => {
//use set helper to set state
this.setState(set(this.state, key, arr => value));
};
isIndeterminate = () =>
!this.isEverythingChecked() &&
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result || get(this.state, key).length,
false
);
toggleEveryting = e => {
const checked = e.target.checked;
this.setState(
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
set(result, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
),
this.state
)
);
};
onToggleAll = (checked, key) => {
this.setState(
//use set helper to set state
set(this.state, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
)
);
};
isEverythingChecked = () =>
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result &&
get(this.state, key).length ===
this.plainOptions.length,
true
);
plainOptions = [
{ value: 1, name: 'Apple' },
{ value: 2, name: 'Pear' },
{ value: 3, name: 'Orange' },
];
render() {
return (
<React.Fragment>
<h1>App state</h1>
{JSON.stringify(this.state)}
<div>
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={this.toggleEveryting}
checked={this.isEverythingChecked()}
>
Toggle everything
</Checkbox>
</div>
{[
{ label: 'group', stateKey: this.permissionsKey },
{ label: 'top', stateKey: this.topGroupKey },
{ label: 'other', stateKey: this.someKey },
].map(({ label, stateKey }) => (
<Row
key={label}
options={this.plainOptions}
// use getter to get state selected value
// for this particular group
checked={get(this.state, stateKey)}
label={label}
onChange={this.onChange} //change behaviour from App
onToggleAll={this.onToggleAll} //toggle all from App
//state key to indicate what state needs to change
// used in setState in App and passed to set helper
stateKey={stateKey}
/>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.js"></script>
<div id="root"></div>
I rewrite all the handlers.
The bug in your code is located on the usage of antd Checkbox.Group component with map as a child component, perhaps we need some key to distinguish each of the Row. Simply put them in one component works without that strange state update.
As the demand during communication, the total button is also added.
And, we don't need many states, keep the single-source data is always the best practice.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Checkbox } from "antd";
const group = ["group", "top"];
const groupItems = ["Apple", "Pear", "Orange"];
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
constructor() {
super();
this.state = {
permission: {}
};
}
UNSAFE_componentWillMount() {
this.setDefault(false);
}
setDefault = fill => {
const temp = {};
group.forEach(x => (temp[x] = fill ? groupItems : []));
this.setState({ permission: temp });
};
checkLength = () => {
const { permission } = this.state;
let sum = 0;
Object.keys(permission).forEach(x => (sum += permission[x].length));
return sum;
};
/**
* For total
*/
isTotalIndeterminate = () => {
const len = this.checkLength();
return len > 0 && len < groupItems.length * group.length;
};
onCheckTotalChange = () => e => {
this.setDefault(e.target.checked);
};
isTotalChecked = () => {
return this.checkLength() === groupItems.length * group.length;
};
/**
* For each group
*/
isIndeterminate = label => {
const { permission } = this.state;
return (
permission[label].length > 0 &&
permission[label].length < groupItems.length
);
};
onCheckAllChange = label => e => {
const { permission } = this.state;
const list = e.target.checked ? groupItems : [];
this.setState({ permission: { ...permission, [label]: list } });
};
isAllChecked = label => {
const { permission } = this.state;
return !groupItems.some(x => !permission[label].includes(x));
};
/**
* For each item
*/
isChecked = label => {
const { permission } = this.state;
return permission[label];
};
onChange = label => e => {
const { permission } = this.state;
this.setState({ permission: { ...permission, [label]: e } });
};
render() {
const { permission } = this.state;
console.log(permission);
return (
<React.Fragment>
<Checkbox
indeterminate={this.isTotalIndeterminate()}
onChange={this.onCheckTotalChange()}
checked={this.isTotalChecked()}
>
Check all
</Checkbox>
{group.map(label => (
<div key={label}>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate(label)}
onChange={this.onCheckAllChange(label)}
checked={this.isAllChecked(label)}
>
Check all
</Checkbox>
<CheckboxGroup
options={groupItems}
value={this.isChecked(label)}
onChange={this.onChange(label)}
/>
</div>
</div>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Try it online:
Please try this,
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
permission : { ...prevSatate.permission , { [value]: this.state.checked }}
});
});
};
by using spread operator you can stop mutating the object. same way you can also use object.assign like this.
this.setState(prevState => {
permission : Object.assign({} , prevState.permission, { [value]: this.state.checked });
});
And also i would suggest not to call setState in a callback. If you want to access the current state you can simply use the current checked value which you are getting in the function itself.
so your function becomes ,
onChange = value => checked => {
this.setState({ checked });
this.setState(prevState => {return { permission : { ...prevSatate.permission, { [value]: checked }}
}});
};
Try the following
//Inside constructor do the following
this.state = {checkState:[]}
this.setChecked = this.setChecked.bind(this);
//this.setChecked2 = this.setChecked2.bind(this);
//Outside constructor but before render()
setChecked(e){
this.setState({
checkState : this.state.checkState.concat([{checked: e.target.id + '=>' + e.target.value}])
//Id is the id property for a specific(target) field
});
}
//Finally attack the method above.i.e. this.setChecked to a form input.
Hope it will address your issues

adding more than 1 number value to payload

I am playing around with a redux tutorial code, I am trying to add another number value to createPolicy action creator, it is showing some error, please tell me what am I missing here
console.clear()
const createPolicy = (name, amount) => {
return {
type : 'CREATEPOLICY',
payload : {
name, amount, bill
}
}
}
const deletePolicy = (name) => {
return {
type : 'DELETEPOLICY',
payload : {
name
}
}
}
const claimRequest = (name, amountofclaim, fees) => {
return {
type : 'CLAIMREQUEST',
payload : {
name, amountofclaim, fees
}
}
}
const creteReducer = (previesdelte = [], action) => {
if(action.type === "CREATEPOLICY") {return [...previesdelte, action.payload ]}
else{return previesdelte}
}
const clamReducer = (totalamount=200, action) => {
if(action.type === "CLAIMREQUEST") {
return totalamount - (action.payload.amountofclaim + action.payload.fees)}
else if(action.type === "CREATEPOLICY") {
return totalamount + (action.payload.amount + action.payload.bill)}
else{return totalamount}
}
const deleteReducer = (userlist = [], action) => {
if(action.type === "CREATEPOLICY"){
return [...userlist, action.payload]
}
else if(action.type === "DELETEPOLICY") {
return userlist.filter( name => name !== action.payload.name)
}
else {
return userlist
}
}
const {createStore, combineReducers} = Redux;
const allReducers = combineReducers({
creteReducer:creteReducer,
clamReducer:clamReducer,
deleteReducer:deleteReducer
})
const store = createStore(allReducers)
store.dispatch(createPolicy('fff', 20, 3))
store.dispatch(claimRequest('fff', 150, 5))
console.log(store.getState())
A parameter bill is probably missing from your createPolicy function.

Categories

Resources