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));
};
}
Related
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?
This is my codeSandbox Code! https://codesandbox.io/s/bold-lederberg-d2h508?file=/src/Components/Products/Products.js
Once I click in "default" I want to have the items back to the original presentation please! But I can not do it
sorting by price is working but the default option is giving me problems
import React, { useState } from "react";
import Products from "./Components/Products/Products";
import SearchInput from "./Components/SearchInput/SearchInput";
import data from "./utils/data";
const App = () => {
const [searchValue, setSearchValue] = useState("");
const [productsInfo, setProductsInfo] = useState([]);
const handleChange = (e) => {
setSearchValue(e.target.value);
};
const selectedChangeFilter = (e) => {
const { value } = e.target;
if (value === "sporting goods") {
const sportingGoods = data.filter(
(product) => product.category === "Sporting Goods"
);
setProductsInfo(sportingGoods);
}
if (value === "electronics") {
const electronicsGoods = data.filter(
(product) => product.category === "Electronics"
);
setProductsInfo(electronicsGoods);
}
if (value === "lowest price") {
const lowestPriceGoods = data.sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const highestPriceGoods = data.sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
if (value === "all") {
setProductsInfo(data);
}
};
const searchProducts = (products) => {
if (searchValue.toLowerCase().trim() === "") {
setProductsInfo(products);
} else {
const seekedItem = productsInfo.filter(
(product) =>
product.name.toLowerCase().trim().includes(searchValue) ||
product.category.toLowerCase().trim().includes(searchValue)
);
setProductsInfo(seekedItem);
}
};
return (
<div>
<SearchInput
handleChange={handleChange}
searchValue={searchValue}
selectedChangeFilter={selectedChangeFilter}
/>
<Products
data={data}
searchValue={searchValue}
productsInfo={productsInfo}
searchProducts={searchProducts}
/>
</div>
);
};
export default App;
I tried this one below but doesn't work
if (value === "all") {
const copiedData = [...data]
setProductsInfo(copiedData);
}
This is because while sorting you are sorting on your original data array. So the item ordering changes in it. You can copy your data into a seperate array and sort then as shown below.
Updated Codesandbox Link - https://codesandbox.io/s/fervent-napier-3rhvs4?file=/src/App.js
if (value === "lowest price") {
const productsToSort = [...data]
const lowestPriceGoods = productsToSort.sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const productsToSort = [...data]
const highestPriceGoods = productsToSort.sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
I think it's because data.sort() change your javascript data object, and doesn't return a new array like filter.
Try by replacing data.sort() by [...data].sort()
const selectedChangeFilter = (e) => {
const { value } = e.target;
if (value === "sporting goods") {
const sportingGoods = data.filter(
(product) => product.category === "Sporting Goods"
);
setProductsInfo(sportingGoods);
}
if (value === "electronics") {
const electronicsGoods = data.filter(
(product) => product.category === "Electronics"
);
setProductsInfo(electronicsGoods);
}
if (value === "lowest price") {
const lowestPriceGoods = [...data].sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const highestPriceGoods = [...data].sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
if (value === "all") {
setProductsInfo(data);
}
};
This is happening because Array.sort() method is mutable. You are sorting the data array and returning the sorted value. This is a short example of what is happening.
const fruits = ["Melon", "Avocado", "Apple"];
console.log(fruits) // ["Melon", "Avocado", "Apple"]
fruits.sort()
console.log(fruits) // [ 'Apple', 'Avocado', 'Melon' ]
In order to avoid that in your code you can destructure the data array. This will create a new array pointing it to a different place in the memory.
const selectedChangeFilter = (e) => {
const { value } = e.target;
// { . . . }
if (value === "lowest price") {
const lowestPriceGoods = [...data].sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const highestPriceGoods = [...data].sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
// { . . . }
}
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
//
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 };
Here I'm working on AutoComplete and Auto fill of react.
I'm trying to convert it to react hooks as I have written all of my code is in hooks only.
I've to some level converted it to hooks based as per my understanding. But I'm not able to completely convert it.
Original code
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
item: {
code: "",
name: "",
unit: "",
rate: ""
},
cursor: 0,
searchItems: []
};
this.autocomplete = this.autocomplete.bind(this);
this.handleKeyup = this.handleKeyup.bind(this);
this.handleKeydown = this.handleKeydown.bind(this);
this.handleListKeydown = this.handleListKeydown.bind(this);
this.selectItem = this.selectItem.bind(this);
this.handleChange = this.handleChange.bind(this);
}
autocomplete(evt) {
let text = evt.target.value;
fetch(`https://invoiceitems.herokuapp.com/items?name_like=${text}&_limit=6`)
.then((res) => res.json())
.then((data) => {
this.setState({ searchItems: data });
});
}
handleKeyup(evt) {
if (evt.keyCode === 27) {
this.setState({ searchItems: [] });
return false;
}
}
handleKeydown(evt) {
const { cursor, searchItems } = this.state;
// arrow up/down button should select next/previous list element
if (evt.keyCode === 38 && cursor > 0) {
this.setState((prevState) => ({
cursor: prevState.cursor - 1
}));
} else if (evt.keyCode === 40 && cursor < searchItems.length - 1) {
this.setState((prevState) => ({
cursor: prevState.cursor + 1
}));
}
if (evt.keyCode === 13) {
let currentItem = searchItems[cursor];
if (currentItem !== undefined) {
const { name, code, rate, unit } = currentItem;
this.setState({ item: { name, code, rate, unit }, searchItems: [] });
}
}
if (evt.keyCode === 8) {
this.setState({ item: { name: "", code: "", rate: "", unit: "" } });
}
}
selectItem(id) {
const { searchItems } = this.state;
let selectedItem = searchItems.find((item) => item.code === id);
const { code, name, unit, rate } = selectedItem;
this.setState({ item: { code, name, unit, rate } });
this.setState({ searchItems: [] });
}
handleListKeydown(evt) {
console.log(evt.keyCode);
}
handleChange(evt) {
this.setState({ item: { [evt.target.name]: evt.target.value } });
}
render() {
const { searchItems, cursor, item, handleChange } = this.state;
const { code, name, unit, rate } = item;
return (
<div className="container mt-3">
<h1 className="h2 text-center">Autocomplete Example</h1>
<div className="form-group">
<label htmlFor="autocomplete">Item Name </label>
<input
type="text"
id="autocomplete"
onChange={this.autocomplete}
onKeyUp={this.handleKeyup}
onKeyDown={this.handleKeydown}
value={name}
className="custom-input form-control"
/>
{searchItems.length > 0 && (
<ul className="list-group">
{searchItems.map((item, idx) => (
<li
className={
cursor === idx
? "active list-group-item"
: "list-group-item"
}
key={idx}
onClick={() => this.selectItem(item.code)}
onKeyDown={(evt) => this.handleListKeydown(evt, item.code)}
>
{item.name}
</li>
))}
</ul>
)}
</div>
</div>
);
}
}
export default App;
Link to original code: https://codepen.io/regexp/details/RwPNaLe
Using hooks
Here is the code that I tried to convert to hooks.
import React, { useState } from "react";
export default function FunctionName(props) {
const [item, setItem] = useState({
vendorNameData: invoiceDetail[0].invoiceData.vendor,
vendorAccountData: invoiceDetail[0].invoiceData.vendaAccount,
vendorAddressData: invoiceDetail[0].invoiceData.vendorAddress
});
const [cursor, setCursor] = useState(0);
const [searchItems, SetSearchItems] = useState([]);
function AutoComplete(evt) {
let text = evt.target.value;
console.log(text);
fetch(`https://invoiceitems.herokuapp.com/items?name_like=${text}&_limit=6`)
.then((res) => res.json())
.then((data) => {
SetSearchItems(data);
});
}
function HandleKeyUp(evt) {
if (evt.keyCode === 27) {
SetSearchItems([]);
return false;
}
}
function HandleKeyDown(evt) {
// const [cursor, setCursor] = useState();
// const [searchItems, SetSearchItems] = useState()
if (evt.keyCode === 38 && cursor > 0) {
setCursor((cursor) => ({ cursor: cursor + 1 }));
} else if (evt.keyCode === 40 && cursor < searchItems.length - 1) {
setCursor((cursor) => ({ cursor: cursor + 1 }));
}
if (evt.keyCode === 13) {
let currentItem = searchItems[cursor];
if (currentItem !== undefined) {
const {
vendorNameData,
vendorAccountData,
vendorAddressData
} = currentItem;
setItem({ vendorNameData, vendorAccountData, vendorAddressData });
SetSearchItems([]);
}
}
if (evt.keyCode === 8) {
setItem({
vendorNameData: "",
vendorAccountData: "",
vendorAddressData: ""
});
}
}
function SelectItem(id) {
const [searchItems, SetSearchItems] = useState();
let selectedItem = searchItems.find((item) => item.code === id);
const {
vendorNameData,
vendorAccountData,
vendorAddressData
} = selectedItem;
setItem({ vendorNameData, vendorAccountData, vendorAddressData });
SetSearchItems([]);
}
function HandleListKeyDown(evt) {
console.log(evt.keyCode);
}
function HandleChange(evt) {
setItem({ item: { [evt.target.name]: evt.target.value } });
}
}
It would be really helpful to point me out where I'm lagging. I've tried my best but this is what I could come up with.
Any help would really be appreciated.
I've 'traduced' and cleaned up your original component, resulting as follows: (please see notes below)
import React, {useState, useCallback} from 'react';
function Input() {
const [item, setItem] = useState({
code: '',
name: '',
unit: '',
rate: ''
});
const [cursor, setCursor] = useState(0);
const [searchItems, setSearchItems] = useState([]);
const autocomplete = useCallback((evt) => {
const text = evt.target.value;
fetch(
`https://invoiceitems.herokuapp.com/items?name_like=${text}&_limit=6`
)
.then((res) => res.json())
.then((data) => {
setSearchItems(data);
});
}, []);
const handleKeyup = useCallback((evt) => {
if (evt.keyCode === 27) {
setSearchItems([]);
return false;
}
return true;
}, []);
const handleKeydown = useCallback(
(evt) => {
// arrow up/down button should select next/previous list element
if (evt.keyCode === 38 && cursor > 0) {
setCursor((prevCursor) => prevCursor - 1);
} else if (evt.keyCode === 40 && cursor < searchItems.length - 1) {
setCursor((prevCursor) => prevCursor + 1);
}
if (evt.keyCode === 13) {
let currentItem = searchItems[cursor];
if (currentItem !== undefined) {
const {code, name, unit, rate} = currentItem;
setItem({code, name, unit, rate});
setSearchItems([]);
}
}
if (evt.keyCode === 8) {
setItem({code: '', name: '', unit: '', rate: ''});
}
},
[cursor, searchItems]
);
const selectItem = useCallback(
(id) => {
let selectedItem = searchItems.find((item) => item.code === id);
const {code, name, unit, rate} = selectedItem;
setItem({code, name, unit, rate});
setSearchItems([]);
},
[searchItems]
);
const handleListKeydown = useCallback((evt) => {
console.log(evt.keyCode);
}, []);
return (
<div className={'container mt-3'}>
<h1 className={'h2 text-center'}>{'Autocomplete Example'}</h1>
<div className={'form-group'}>
<label htmlFor={'autocomplete'}>{'Item Name'}</label>
<input
type={'text'}
id={'autocomplete'}
onChange={autocomplete}
onKeyUp={handleKeyup}
onKeyDown={handleKeydown}
value={item.name}
className={'custom-input form-control'}
/>
{searchItems.length > 0 && (
<ul className={'list-group'}>
{searchItems.map((item, idx) => (
<li
className={
cursor === idx
? 'active list-group-item'
: 'list-group-item'
}
key={idx}
onClick={() => selectItem(item.code)}
onKeyDown={handleListKeydown}>
{item.name}
</li>
))}
</ul>
)}
</div>
</div>
);
}
export {Input};
useState replaces Class Components State management. It is good practice to split your state into smaller pieces as possible, because new values will completely replace old ones (there is no merging like Class Components this.setState does). Using cursor as an example, do const [cursor, setCursor] = useState(0); to initialize your state (in this case initial state is 0). Then use cursor to use the value and call setCursor(newValue) to update it. Each time you update your state you will most likely trigger a re-rendering.
useCallback allows you to only re-declare a function when its dependencies have changed, thus improving performance. Function dependencies are specified in the second argument array. On each render, React will compare the new values with the old ones and will always return the same function when dependencies have not changed.
The return statement replaces your previous render method.
Follows a working snippet. Please note that OP original code behavior has not been changed.
const {useState, useCallback, StrictMode} = React;
function Input() {
const [item, setItem] = useState({
code: '',
name: '',
unit: '',
rate: ''
});
const [cursor, setCursor] = useState(0);
const [searchItems, setSearchItems] = useState([]);
const autocomplete = useCallback((evt) => {
const text = evt.target.value;
fetch(
`https://invoiceitems.herokuapp.com/items?name_like=${text}&_limit=6`
)
.then((res) => res.json())
.then((data) => {
setSearchItems(data);
});
}, []);
const handleKeyup = useCallback((evt) => {
if (evt.keyCode === 27) {
setSearchItems([]);
return false;
}
return true;
}, []);
const handleKeydown = useCallback(
(evt) => {
// arrow up/down button should select next/previous list element
if (evt.keyCode === 38 && cursor > 0) {
setCursor((prevCursor) => prevCursor - 1);
} else if (evt.keyCode === 40 && cursor < searchItems.length - 1) {
setCursor((prevCursor) => prevCursor + 1);
}
if (evt.keyCode === 13) {
let currentItem = searchItems[cursor];
if (currentItem !== undefined) {
const {code, name, unit, rate} = currentItem;
setItem({code, name, unit, rate});
setSearchItems([]);
}
}
if (evt.keyCode === 8) {
setItem({code: '', name: '', unit: '', rate: ''});
}
},
[cursor, searchItems]
);
const selectItem = useCallback(
(id) => {
let selectedItem = searchItems.find((item) => item.code === id);
const {code, name, unit, rate} = selectedItem;
setItem({code, name, unit, rate});
setSearchItems([]);
},
[searchItems]
);
const handleListKeydown = useCallback((evt) => {
console.log(evt.keyCode);
}, []);
return (
<div className={'container mt-3'}>
<h1 className={'h2 text-center'}>{'Autocomplete Example'}</h1>
<div className={'form-group'}>
<label htmlFor={'autocomplete'}>{'Item Name'}</label>
<input
type={'text'}
id={'autocomplete'}
onChange={autocomplete}
onKeyUp={handleKeyup}
onKeyDown={handleKeydown}
value={item.name}
className={'custom-input form-control'}
/>
{searchItems.length > 0 && (
<ul className={'list-group'}>
{searchItems.map((item, idx) => (
<li
className={
cursor === idx
? 'active list-group-item'
: 'list-group-item'
}
key={idx}
onClick={() => selectItem(item.code)}
onKeyDown={handleListKeydown}>
{item.name}
</li>
))}
</ul>
)}
</div>
</div>
);
}
ReactDOM.render(
<StrictMode>
<Input />
</StrictMode>,
document.getElementById('root')
);
.active {
color: #ff0000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id='root' />