Vue 3 search component: Maximum call stack size exceeded - javascript

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?

Related

REACT- My condition is working partially why?

In my json file from my api, I have a variable apply, If apply:0, Then It should should print my data and If apply:1, Then It should hide my data (i.e row in my Table). Actually when I combine my 2 conditions, It is partially working :(applyStatus ? menus : menus.filter((i) => i.apply !== 1)) && (showGood ? menus : menus.filter((i) => i.taste !== "Good")), i.e Only the ...showGood ?... condition is working and the ...applyStatus?... not working.
Whereas if I do only :
<Table data={matchData && (applyStatus ? menus : menus.filter((i) => i.apply !== 1))> . Then, I have my data where apply:0 are displayed.
What's wrong in my code please ?
export default function MenuDisplay() {
const { menuId } = useParams();
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus]=useState([])
const [showGood, setShowGood] = useState(false);
const [applyStatus, setApplyStatus] = useState(false);
if (menus.apply === 0) {
setApplyStatus(true)
}
if (menus.apply === 1) {
setApplyStatus(false)
}
useEffect (() => {
axios.post("",{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
useEffect (() => {
const GoodMenus = menus.filter((i) => i.taste === "Good");
const restOfMenus = menus.filter((i) => i.taste !== "Good");
setMenus([...GoodMenus, ...restOfMenus]);
}, [menus]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Status",
accessor: (row) => row.status
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
return (
<div>
<button type="button" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
show good
<Toggle value = {showGood} onChange={() => setShowGood(!showGood)} />
<Table
data={matchData &&(applyStatus ? menus : menus.filter((i) => i.apply !== 1)) &&
(showGood ? menus : menus.filter((i) => i.taste !== "Good"))}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
{
"menuId": 1,
"_id": "123ml66",
"name": "Pea Soup",
"description": "Creamy pea soup topped with melted cheese and sourdough croutons.",
"dishes": [
{
"meat": "N/A",
"vegetables": "pea"
}
],
"taste": "Good",
"comments": "3/4",
"price": "Low",
"availability": 0,
"trust": 1,
"status": "Pending",
"apply": 1
},
//...other data
]
The problem is actually in the usage of your parenthesis. Your current logic looks like this.
(applyStatus ?
menus :
menus.filter((i) => i.apply !== 1)
) && (showGood ?
menus :
menus.filter((i) => i.taste !== "Good")
)
If you simplify it it looks like
(Condition ? array : filteredArray) && (Condition ? array : filteredArray)
If you simplify it again it results in
array && array
When you are using && operator on two array like you are doing it will return you the second array which in your case is the array filter on i.taste !== "Good".
I tried to show it with a code sample below
const array = [{price:5, age:10},{price:3, age:8},{price:9, age:12},{price:12, age:13}];
const ageFiltered = array.filter(x => x.age < 11);
const priceFiltered = array.filter(x => x.price > 4);
console.log("Age filtered:" + JSON.stringify(ageFiltered));
console.log("Price filtered:" + JSON.stringify(priceFiltered));
const result = ageFiltered && priceFiltered;
console.log("&& operator:" + JSON.stringify(result));
const doubleFiltered = array.filter(x => x.age < 11 && x.price > 4);
console.log("Dobule filters:" + JSON.stringify(doubleFiltered));
To fix your issue your code must be update to be like this
menus.filter(i => (applyStatus ? true : i.apply !== 1) && (showGood ? true : i.taste !== "Good") )
Your code is not working because && only return the value of the last operand. So i think your code should be this
<Table
data={
matchData && menus.filter(i => {
if (applyStatus) return true;
return i.apply !== 1;
}).filter(i => {
if (showGood) return true;
return i.taste !== 'Good'
})
}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
if (menus.apply === 0) {
setApplyStatus(true)
}
if (menus.apply === 1) {
setApplyStatus(false)
}
from my understanding menus is an array so there is nothing like menus.apply so its either you loop the menus checking if apply is true. then there is no need for
===0 just check if its true(1) or false(0).
if you want to display those that has apply you can sort them by using map just after fetching them like this
useEffect (() => {
axios.post("",{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
let data_ =res.data.menus
let_apply_true = []
let apply_false = []
data_.map((data)=>{
if(data.apply){
let_apply_true.push(data)
}
//else will return those that are false
let apply_false.push(data)
})
setApplyMenus(let_apply_true) //
setNotApplyMenus(let apply_false)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
//that is just one way
//

How to remove items from the Cart?

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

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

React | JavaScript : Unable to update an array using map

I am working on a restaurant app and need to update the the orders feature.
LOGIC
= If the item added by clicking "+" :
It needs to check if the order already exists (if yes, then
increment the quantity)
If not then append that item to the
orders list
CODE:
let orders = [];
const addMore = (e) => {
let sign = e.target.innerText;
// if the item already exists in orders
const updatedOrder = orders.map(each => {
if( each.id === e.target.id && sign === "+"){
return {
...each, qty: each.qty + 1
}
}
else if (each.id === e.target.id && sign === "-"){
return {
...each, qty: each.qty - 1
}
} else return each;
});
console.log(updatedOrder); // < ------ SHOWS EMPTY !!
orders = updatedOrder;
fullMenu.map(each => {
if(each._id === e.target.id && sign === '+') {
setTotal( total + parseInt(each.price) );
orders.push({
id: each._id,
name: each.name,
price: each.price,
type: each.isVeg ? "veg" : "non-veg",
qty: 1
});
}
});
}
Issue : It's not running the updatedOrder loop. It shows empty.
DATA SCHEMA:
{
_id: 5eef61450bd95e1f5c8f372f
name: "Burger"
category: "American"
price: "100"
isVeg: false
__v: 0
}
I think you should store orders also in a state
Example
const { useState, useEffect } = React;
const App = () => {
const [products, setProducts] = useState([{
id: "5eef61450bd95e1f5c8f372f",
name: "Burger",
category: "American",
price: "100",
isVeg: false,
__v: 0
}])
const [orders, setOrders] = useState([])
const [total, setTotal] = useState(0)
const addMore = ({target: {id, innerText: sign}}) => {
const newOrders = [...orders];
let order = newOrders.find(pr => pr.id === id);
if(order) {
let _sign = sign === "+" ? 1 : -1;
order.qty = (order.qty || 0) + 1 * _sign
if(!order.qty) {
setOrders(orders => orders.filter(pr => pr.id !== id));
setTotal(total => total - parseInt(order.price));
return;
}
setOrders(newOrders);
setTotal(total => total + _sign * parseInt(order.price));
return;
}
const product = products.find(pr => pr.id === id);
order = {
...product,
qty: 1
}
setOrders(orders => [...orders, order]);
setTotal(total => total + parseInt(order.price));
}
return <div>
{products.map(({id, name, category, price}) => <div className="product" key={id}>
<div>{name} {category} {price}$</div>
<button id={id} onClick={addMore}>+</button>
<button id={id} onClick={addMore}>-</button>
</div>)}
<div>Total: {total}</div>
<div>{JSON.stringify(orders, null, 4)}</div>
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
.product {
padding: 10px;
border: 1px solid black;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
const addMore = (e) => {
let sign = e.target.innerText;
let id = e.target.id;
const newOrders = [...orders];
let order = newOrders.find(each => each.id === id);
// if the order already exists for the item
if (order) {
let _sign = sign === "+" ? 1 : -1;
// increase or decrease the quantity
order.qty = (order.qty || 0) + 1 * _sign;
// remove item from order list if qty = 0
if(!order.qty){
setOrders(orders => orders.filter(pr => pr.id !== id));
// setTotal(total => total - parseInt(order.price));
return;
};
// save
setOrders(newOrders);
if (sign === "+") setTotal(total => total + parseInt(order.price));
if (sign === "-") setTotal(total => total - parseInt(order.price));
return;
};
// if item doesn't exist in the order list
const item = fullMenu.find(each => each._id === id);
if (sign === "+") {
order = {
...item, qty: 1
};
setOrders(orders => [...orders, order]);
setTotal(total => total + parseInt(order.price));
};
}

Self writen MaskedInput caused infinite loop

I wrote a number input with thousand separator, everything is so smooth util I press a number than press backspace and press a number again (do it fast), it causes an infinite loop where my input value keep changing.
Please have a look and let me know where is the failure in my logic.
import React from 'react';
import { TextInput } from 'react-native';
const addSeparator = (input, divider) => {
return Number(input.toString())
.toFixed(0)
.replace(/(\d)(?=(\d{3})+(?!\d))/g, `$1${divider || '.'}`);
};
const removeNonDigits = (value) => {
const formatedValue = !value && value !== 0 ? '' : value.toString();
return formatedValue.replace(/\D/g, '');
};
const MaskedNumberInput = (
{ onChangeText, value, maxValue, ...props },
ref,
) => {
const [maskedValue, setMaskedValue] = React.useState(
value === '' ? value : addSeparator(value),
);
React.useEffect(() => {
onMaskedInputChangeText(value);
}, [value]);
React.useEffect(() => {
if (onChangeText) {
onChangeText(removeNonDigits(maskedValue));
}
}, [maskedValue]);
const onMaskedInputChangeText = (newValue) => {
let digitValue = removeNonDigits(newValue);
if (
maxValue !== '' &&
maxValue !== null &&
maxValue !== undefined &&
digitValue > maxValue
) {
digitValue = maxValue;
}
if (removeNonDigits(maskedValue) === digitValue) {
console.tron.log('return');
return;
}
const maskedDigitValue =
digitValue === '' ? digitValue : addSeparator(digitValue);
setMaskedValue(maskedDigitValue);
};
return (
<TextInput
ref={ref}
value={maskedValue}
onChangeText={onMaskedInputChangeText}
keyboardType="number-pad"
{...props}
/>
);
};

Categories

Resources