In my state I have an array, which I mapped in render function:
State
this.state = {
newItem: {
name: "",
amount: 0
},
products: [
{
name: "Item",
amount: 5
},
{
name: "Item",
amount: 5
},
{
name: "Item",
amount: 5
}
]
}
I want to delete element from the array products by clicking in button of that element. But what I attempted to do, everything fails. I searched a lot and find logically the most common solution but it didn't work too. Here is the delete function:
Delete function
delete(e) {
this.setState(prevState => ({ products: prevState.products.filter((product) => {
return product !== e.target.value
})}))
console.table(this.state.products)
}
Mapped JSX code
{
this.state.products.map((item, index) => {
return(
<div key={index}>
<input readOnly value={this.state.products[index].name} type="text" />
<button disabled>-</button>
<input readOnly value={this.state.products[index].amount} type="number" />
<button disabled>+</button>
<button onClick={(e) => this.delete(e)}>Delete</button>
</div>
)
})
}
You need to pass index of that element into delete which will be removed like below.
<button onClick={() => delete(index)}>Delete</button>
Then you can remove that element easily.
delete = index => {
this.setState(prevState =>
({
products: prevState.products.filter((product, i) => i !== index)
})
);
}
Note that setState is asynchronous, so using console.log(this.state.products) immediately after calling setState will not print the updated products. You can pass a callback in the second parameter of setState, it will be executed once setState is completed.
The other problem comes from the delete function. You are using e.target.value which is an empty string because the button has no value.
You can fix this with the following code :
delete(product) {
this.setState(prevState => ({
products: prevState.products.filter(p => p !== product)
}),
() => console.log(this.state.products));
}
In the JSX code, I pass the product in the first argument of the delete function :
{
this.state.products.map((product, index) => (
<div key={index}>
<input readOnly value={product.name} type="text"/>
<button disabled>-</button>
<input readOnly value={product.amount} type="number"/>
<button disabled>+</button>
<button onClick={() => this.delete(product)}>Delete</button>
</div>
))
}
A simplified example of how to make item specific buttons on a mapped list:
delete = (id) => {
this.setState(products: this.state.products.filter((item, index) => index !== idx));
}
here is the solution and working example here https://codesandbox.io/s/dawn-tdd-js8yz
state = {
newItem: {
name: "",
amount: 0
},
products: [
{
name: "Item1",
amount: 5
},
{
name: "Item2",
amount: 5
},
{
name: "Item3",
amount: 5
}
]
}
delete(e: any) {
var products = this.state.products.filter((product) => {
return product.name !== e.name
});
console.log(products)
this.setState({products: products})
console.table(this.state.products)
}
render() {
const { products } = this.state;
return (
<>
{products.map((x, index) => {
return <div key={index}>
<input readOnly value={this.state.products[index].name} type="text" />
<button disabled>-</button>
<input readOnly value={this.state.products[index].amount} type="number" />
<button disabled>+</button>
<button onClick={this.delete.bind(this, x)}>Delete</button>
</div>
})}
</>
)
}
Related
I want add object inside of array items
I am trying to manage objects inside of an array with useState but is not working i have only in object but I want the object in interior of the array of items. When I click add items on the button i want add the element and if possible remove this element when i click remove items in button link with inputs (see the image)
Like :
company:"Apple",
companyAdress:"5 avenue triagle",
items: [
{
itemName: Computer,
itemQuantity: 20,
itemPrice: 209
},
{
itemName: Computer,
itemQuantity: 20,
itemPrice: 209
},
]
My code :
const [info, setInfo] = useState({});
const [itemForm, setItemForm] = useState({ num: 1 })
const handleRemoveItem = (e) => {
e.preventDefault();
setItemForm((itemForm) => ({ num: itemForm.num - 1 }))
}
const handleAddItem = (e) => {
e.preventDefault();
setItemForm((itemForm) => ({ num: itemForm.num + 1 }))
}
<label>Company</label>
<input onChange={(e) => { setInfo({ ...info, company: e.currentTarget.value}); }} placeholder="Company"></input>
<label>company Adress</label>
<input onChange={(e) => { setInfo({ ...info, companyAdress: e.currentTarget.value }); }} placeholder="Adresse"></input>
<ul className="space-y-3">
{[...Array(itemForm.num)].map((x, i) => {
return (
<li key={i}>
<div>
<input onChange={(e) => { setInfo({...info,itemName: e.currentTarget.value });}} name="itemName" placeholder="itemName:" ></input>
<input onChange={(e) => { setInfo({ ...info, itemQuantity: e.currentTarget.value }); }} type="number" name="itemQuantity" placeholder="Quantity:"></input>
<input onChange={(e) => { setInfo({ ...info, itemPrice: e.currentTarget.value }); }} type="number" name="itemPrice" placeholder="Price:"></input>
<button onClick={handleRemoveItem}>Enlever </button>
<button onClick={handleAddItem}>+ Add New Item</button>
</div>
</li>
)
}
)}
</ul>
i do something like this using an id to find the iteminfo.
i am currying the itemid here but you could put the itemId as part of the input id and find it that way if you like - then you could use one function. anyway hope it helps
also i am just using the id as the key for the object you might what to be more strict on this ¯_(ツ)_/¯
i would also put the factory and defaultInfo else where in your app
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
const defaultItemFactory = () => {
return { itemName: "", itemQuantity: "", itemPrice: "", id: uuidv4() };
};
const defaultInfo = {
company: "",
companyAdress: "",
items: [defaultItemFactory()],
};
function App() {
const [info, setInfo] = useState(defaultInfo);
const changeHanlder = (event) => {
const { id, value } = event.currentTarget;
setInfo((_info) => {
return { ..._info, [id]: value };
});
};
const itemHanlder = (itemId) => (event) => {
const { id, value } = event.currentTarget;
setInfo((_info) => {
if (id === "add")
return { ..._info, items: _info.items.concat(defaultItemFactory()) };
const items = _info.items
.map((item) => {
if (item.id !== itemId) return item;
if (id === "remove") return null;
return { ...item, [id]: value };
})
.filter((out) => out);
return { ..._info, items };
});
};
return (
<div className="App">
<label>Company</label>
<input
id={"company"}
value={info.company}
onChange={changeHanlder}
placeholder="Company"
></input>
<label>company Adress</label>
<input
id={"companyAdress"}
value={info.companyAdress}
onChange={changeHanlder}
placeholder="Adresse"
></input>
<ul className="space-y-3">
{info.items &&
info.items.map((item, i) => {
return (
<li key={`item-${item.id}`}>
<div>
<input
id={"itemName"}
value={item.itemName}
onChange={itemHanlder(item.id)}
name="itemName"
placeholder="itemName:"
></input>
<input
id={"itemQuantity"}
value={item.itemQuantity}
onChange={itemHanlder(item.id)}
type="number"
name="itemQuantity"
placeholder="Quantity:"
></input>
<input
id={"itemPrice"}
value={item.itemPrice}
onChange={itemHanlder(item.id)}
type="number"
name="itemPrice"
placeholder="Price:"
></input>
<button id={"remove"} onClick={itemHanlder(item.id)}>
Enlever{" "}
</button>
<button id={"add"} onClick={itemHanlder(item.id)}>
+ Add New Item
</button>
</div>
</li>
);
})}
</ul>
</div>
);
}
export default App;
I have a small restaurant app which renders a menu and lets users place an order. The parent component receives an array of menu items and renders a child component for each item. The quantity of each item is initialized at the child component ('menu-item') and is also saved in local storage so if a user refreshes the page the order persists. Once the order is submitted, local storage is cleared by the parent component but this change is not picked up by the child. As a result, users see the order quantity even after the order has been submitted. I would like the order quantity to be reset to 0 after the order is submitted. Below is the condensed codebase.
//Parent
const TotalStore = {
total: 0,
reset_timer: setTimeout(() => {}, 0),
compute() {
let newTotal = 0;
let types = new Map<string, boolean>();
TotalStore.items.forEach((value: TotalStoreMenuItem) => {
newTotal += value.price * value.count;
types.set(value.type, true);
});
if (TotalStore.total !== newTotal) {
clearTimeout(TotalStore.reset_timer);
TotalStore.reset_timer = setTimeout(() => {
TotalStore.total = newTotal;
TotalStore.onChange(newTotal);
}, 50);
}
},
update(id: number, price: number, count: number, type: string) {
TotalStore.items.set(id, {
id,
price,
count,
type,
});
TotalStore.compute();
},
reset() {
TotalStore.items.clear();
localStorage.clear();
TotalStore.total = 0;
},
onChange(t: number) {},
items: new Map<number, TotalStoreMenuItem>(),
};
const itemChanged = (
id: number,
price: number,
count: number,
type: string
) => {
TotalStore.update(id, price, count, type);
};
useEffect(() => {
apiFetch("menu").then((json) => setMenu(json.menu));
}, []);
async function handleSubmit(e: any) {
e.preventDefault();
const selectedItems = getSelectedItems(TotalStore);
apiFetch("order", "post", { selectedItems })
.then((json) => {
alert("Order has been submitted");
TotalStore.reset();
}
return (
<div>
{menu.length > 0 ? (
<>
<div className="menu">
<div className="menu-title">Food Menu</div>
<form id="menu-form" onSubmit={handleSubmit} autoComplete="off">
<Menu onChange={itemChanged} props={menu} />
<button type="submit" disabled={!orderPlaced(total)}>
Place Order
</button>
</form>
</div>
<div className="order-total">
<h2>
Total: $<span>{total.toFixed(2)}</span>
</h2>
</div>
</>
) : (
<>Loading Menu</>
)}
</div>
);
//Menu
export default function Menu({ onChange, props }: MenuProps) {
return (
<div>
{props.map((food: any, index: number) => {
return (
<MenuItem
key={index}
onChange={onChange}
type={food.type}
item={food}
/>
);
})}
</div>
);
}
//Menu-Item
const FoodItemLocalStore = {
setCount(id: Number, count: Number) {
localStorage.setItem(`menu_item_id_${id}`, String(count));
},
getCount(id: Number) {
return parseInt(localStorage.getItem(`menu_item_id_${id}`) || "0");
},
};
export default function MenuItem({ onChange, item, type }: MenuItemProps) {
const [data, setData] = useState({
count: FoodItemLocalStore.getCount(item.id),
});
const menuItemCountChange = (e: any) => {
data.count = parseInt(e.target.value);
FoodItemLocalStore.setCount(item.id, data.count);
setData({ ...data });
};
onChange(item.id, item.price, data.count, type);
return (
<div>
<article className="menu-item" data-item-type={type}>
<h3 className="item-name">{item.name}</h3>
<input
type="number"
className="menu-item-count"
min="0"
value={data.count}
onChange={menuItemCountChange}
/>
<strong className="item-price">${item.price.toFixed(2)}</strong>
</article>
</div>
);
}
The code to get tables depends on how many items are selected in the left window in my image below but don't know What I need to write in the onClick function of the button to move the first selected item to the first table GMV1 and the second selected item to GMV2 and so on.
What I want to do is first check the status of contractLineItemSelectionChecked. If the status is true, check the selected items have different contract ids and push the different contract id objects to different places.
[
{
cNo: "CDH0000403",
contractLineItemSelectionChecked: true,
companyCode: "1360",
profitCenter: "DHA1",
approverId: "C7937"
},
{
cNo: "CDH0000404",
contractLineItemSelectionChecked: false,
companyCode: "1360",
profitCenter: "DHA1",
approverId: "C7937"
},
{
cNo: "CDH0000405",
contractLineItemSelectionChecked: true,
companyCode: "1360",
profitCenter: "DHA1",
approverId: "C7937"
}
];
<Field
name="transactionSelected"
component="select"
className="custom-select my-1 mr-sm-2"
id="moveContractLineItems"
onChange={e => {
setFieldValue("transactionSelected", "GMV+ /contract");
document.getElementById("creategoodsMovementsContractsTransaction").click();
getIndex();
}}
>
<option key="select" value="">
Select
</option>
<option key="GMV+ /contract" value="GMV+ /contract">
GMV+ /contract
</option>
<option key="PMT+/contract" value="PMT+/contract">
PMT+/Contract
</option>
</Field>;
<FieldArray name={`transactions["goodsMovements"].transactionItems`}>
{({ push }) => (
<input
type="button"
hidden
id="creategoodsMovementsContractsTransaction"
onClick={() => {
let myCounter = 0;
checkedCheckboxes.forEach(v =>
v.contractLineItemSelectionChecked ? myCounter++ : v
);
for (var i = 0; i < myCounter; i++) {
push({
name:
"GMV" +
(typeof values.transactions.goodsMovements.transactionItems !==
"undefined" &&
values.transactions.goodsMovements.transactionItems.length + 1),
transactionId: "",
transactionType: "1",
requestedPostingDate: null,
companyCode: "",
profitCenter: "",
attachments: [],
comments: "",
transactionLineItems: []
});
}
}}
/>
)}
</FieldArray>;
<button
type="button"
className="btn btn-primary my-1"
onClick={() => moveItems()}
>
Move
</button>;
Maybe the following will give you an idea how to do this:
const Item = React.memo(
//use React.memo to create pure component
function Item({ item, onChange, checked }) {
console.log('rendering:', item.id)
// can you gues why prop={new reference} is not a problem here
// this won't re render if props didn't
// change because it's a pure component
return (
<div>
<div>{item.name}</div>
<input
type="checkbox"
checked={checked}
onChange={() => onChange(item.id, !checked)}
/>
</div>
)
}
)
const SelectedItem = React.memo(
//use React.memo to create pure component
function SelectedItem({ item }) {
console.log('rendering selected:', item.id)
return (
<div>
<div>{item.name}</div>
</div>
)
}
)
class Parent extends React.PureComponent {
state = {
data: [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' }
],
checked: {}
}
toggle = (_id, add) => {
const newChecked = add
? {
...this.state.checked,
[_id]: true
}
: // cannot use Object.fromEntries because SO babel is too old
Object.entries(this.state.checked)
.filter(([key]) => Number(key) !== _id)
.reduce((result, [key, value]) => {
result[key] = value
return result
}, {})
this.setState({ checked: newChecked })
}
render() {
return (
<div>
<div>
<h4>items</h4>
{this.state.data.map(item => (
<Item
item={item}
onChange={this.toggle}
checked={Boolean(this.state.checked[item.id])}
key={item.id}
/>
))}
</div>
<div>
<h4>Selected items</h4>
{this.state.data
.filter(item => this.state.checked[item.id])
.map(item => (
<SelectedItem item={item} key={item.id} />
))}
</div>
</div>
)
}
}
//render app
ReactDOM.render(<Parent />, document.getElementById('root'))
<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>
<div id="root"></div>
Fiddle Demo
I'm trying to update the state array which contains an array of data objects.
State in constructor:
this.state = {
data: [
{ name: "Abc", age: 12 },
{ name: "Xyz", age: 11 }
]
}
Handlers:
handleName(idx, e){
const data = this.state.data.map((item, sidx) => {
if (idx !== sidx) return item;
return { ...item, name: e.target.value };
});
this.setState({ data: data });
}
handleAge(index, e){
const data = this.state.data.map((item, sidx) => {
if (idx !== sidx) return item;
return { ...item, age: e.target.value };
});
this.setState({ data: data });
}
Render Method :
render() {
console.log('render');
const Input = () => this.state.data.map((item, index) =>{
return (
<div>
<input type="text" value={item.name} onChange={(e) => this.handleName(index, e)}/>
<input type="text" value={item.age} onChange={(e) => this.handleAge(index, e)}/>
</div>
);
});
return (
<div>
<Input/>
</div>
)
}
}
I know in every keystroke the render method is refreshing the dom, is something wrong with my rendering elements.
Any suggestions ?
While returning element from within the map you must have a key assigned to them otherwise a new instance of it will be created always on a new render. Also you must not create a component within render
render() {
console.log('render');
return (
<div>
{this.state.data.map((item, index) =>{
return (
<div key={index}>
<input type="text" value={item.name} onChange={(e) => this.handleName(index, e)}/>
<input type="text" value={item.age} onChange={(e) => this.handleAge(index, e)}/>
</div>
);
})}
</div>
)
}
}
Working DEMO
Because on every re-render it is creating a new instance of the component woth values from state,
You need to take the function out of render or simply just use it like this
render() {
console.log('render');
return (
<div>
{this.state.data.map((item, index) =>{
return (
<div>
<input type="text" value={item.name} onChange={(e) => this.handleName(index, e)}/>
<input type="text" value={item.age} onChange={(e) => this.handleAge(index, e)}/>
</div>);})
}
</div>
)
}
}
Demo
My state :
this.state = {
name: '',
subCatagory: [{ name: '', price: '', customize: [] }],
};
my form in react.js :
{this.state.subCatagory.map((subCatagory, idx) => (
<div className="subCatagory" key={idx}>
<input
type="text"
placeholder={`Enter Dish #${idx + 1} name`}
value={subCatagory.name}
onChange={this.handlesubCatagoryNameChange(idx)}
/>
<input
type="number"
placeholder={`subCatagory #${idx + 1} price`}
value={subCatagory.price}
onChange={this.handlesubCatagoryPriceChange(idx)}
/>
<button
type="button"
onClick={this.handleRemovesubCatagory(idx)}
className="small"
>
Delete
</button>
<button type="button" onClick={this.addNewCust(idx)} className="small">
is cust availble?
</button>
{subCatagory.customize.map((customize, custIdx) => (
<div key={custIdx}>
<input
type="text"
placeholder={`subCatagory #${custIdx + 1} price`}
value={customize.name}
onChange={this.handlesubCatagoryChange(
idx,
'customize',
custIdx
)}
/>
</div>
))}
</div>
))}
i want to update every time when a value is updated in input handlesubCatagoryChange and my form is dynamic one ., here i took index of first map and then index of second map but can't able to update the state in react
You can update the item in the array with a function like this
handlesubCatagoryNameChange = idx => e => {
const value = e.target.value;
this.setState(prevState => ({
...prevState,
subCatagory: subCatagory.map((x, i) => {
if (i === idx) {
return {
...x,
name: value
}
}
return x
})
}))
}
(this is only for the name, you will need the same method for the other fields)
This will keep your subCategory array immutable, and only update the item on the specified index.
For the nested array, you will do the same - something like this (if I understand correctly)
handlesubCatagoryChange = (otherIdx, propertyPath, innerIdx) => e => {
const value = e.target.value;
this.setState(prevState => ({
...prevState,
subCatagory: subCatagory.map((x, i) => {
if (i === otherIdx) {
return {
...x,
[propertyPath]: x[propertyPath].map((y, j) => {
if (j === innerIdx) {
return {
...y,
name: value
}
}
return y
})
}
}
return x
})
}))
}
Change button to use event as well
{this.handlesubCatagoryNameChange.bind(this,idx)}
Then use Object.assign
handlesubCatagoryNameChange(idx,e){
let subCatagory = Object.assign({}, this.state.subCatagory); //creating copy of object in state
subCatagory[idx].name = e.target.value
this.setState({ subCatagory });
};