how to update array of object in react state based on id - javascript

i'm new learning react. i have study case with basic operation (CRUD) in react. i create react saving app for CRUD expense or income.
i have three components i.e InputData for input data, TotalMoney for display total money,expense and income, and table for display all data.
*nb pemasukan=income,pengeluaran=expense, jumlah=amount
example data like this
this.state: {
items: [
{id:1, tipe:"pemasukan", jumlah:20000},
{id:1, tipe:"pemasukan", jumlah:20000}
]
}
this is image
the problem is when want to update data. like the image,when change the first data what changes is the data afterwards, not the first data. the second problem is when the type of the first data changes from income to expense, total money for total expenditure and total income not changed on display.
what is the best way for updating data in this study case?
this is my function to update data
updateItem = event => {
event.preventDefault();
const updatedTipe = this.state.tipe;
const updatedJumlah = parseInt(this.state.jumlah);
const updatedJudul = this.state.judul;
const updatedItems = Object.assign({}, this.state.items, {
tipe: updatedTipe,
jumlah: updatedJumlah,
judul: updatedJudul
});
console.log(updatedItems);
const itemLists = this.state.itemLists.map(itemList =>
itemList.id === this.state.items.id ? updatedItems : itemList
);
console.log(itemLists);
this.setState({ jumlah: 0, judul: "", itemLists: itemLists });
this.setEditing(false);
};
this is declaration table in component inputdata
<Table
items={items}
itemLists={itemLists}
editing={editing}
editItem={this.editItem}
tipe={tipe}
jumlah={jumlah}
judul={judul}
setEditing={this.setEditing}
deleteItem={this.deleteItem}
onChange={this.onChange}
updateItem={this.updateItem}
/>
this is input value for update data
<tr className="rowHover">
<td>
<select
className="form-control form-table"
name="tipe"
onChange={props.onChange}
>
<option>Pilih</option>
<option value="pengeluaran">Pengeluaran</option>
<option value="pemasukan">Pemasukan</option>
</select>
</td>
<td>
<input
type="number"
className="form-control form-table"
name="jumlah"
value={props.jumlah}
onChange={props.onChange}
/>
</td>
<td>
<input
type="text"
className="form-control form-table"
name="judul"
value={props.judul}
onChange={props.onChange}
/>
</td>
<td>
<button
onClick={props.updateItem}
type="button"
className="btn btn-success buttonStyles"
>
Update
</button>
<button
onClick={() => props.setEditing(false)}
type="button"
className="btn btn-warning buttonStyles"
>
Cancel
</button>
</td>
</tr>
this is the state
this.state = {
id: null,
judul: "",
tipe: "",
jumlah: 0,
pengeluaran: 0,
pemasukan: 0,
totalUang: 0,
items: {},
itemLists: [],
editing: false
};
this is my code
https://github.com/saldhyyoga/test

I have run your code to see what the problem is and I found a solution that works.
You have to make 2 updates:
- the first one is persisting the id of the updated item too
editItem = item => {
this.setState({
editing: true,
id: item.id, // add this line
jumlah: item.jumlah,
tipe: item.tipe,
judul: item.judul
});
};
the second one is to access the edited item data based on the persisted id
updateItem = event => {
event.preventDefault();
const updatedTipe = this.state.tipe;
const updatedJumlah = parseInt(this.state.jumlah);
const updatedJudul = this.state.judul;
const updatedId = this.state.id; // take edited item id
const updatedItems = Object.assign({}, this.state.items[updatedId], { // access the information of the editem item
tipe: updatedTipe,
jumlah: updatedJumlah,
judul: updatedJudul
});
console.log(updatedItems);
const itemLists = this.state.itemLists
.map(itemList =>
itemList.id === this.state.id ? updatedItems : itemList // replace this.state.items.id with this.state.id
);
console.log(itemLists);
this.setState({ jumlah: 0, judul: "", itemLists: itemLists });
this.setEditing(false);
};
Here is link where you can find the updated solution which is working as expected.
Edit
In order to fix the amounts calculation just invoke the amount function at the end of updateItem function:
updateItem = event => {
event.preventDefault();
const updatedTipe = this.state.tipe;
const updatedJumlah = parseInt(this.state.jumlah);
const updatedJudul = this.state.judul;
const updatedId = this.state.id;
const updatedItems = Object.assign({}, this.state.items[updatedId], {
tipe: updatedTipe,
jumlah: updatedJumlah,
judul: updatedJudul
});
console.log(updatedItems);
const itemLists = this.state.itemLists.map(itemList =>
itemList.id === this.state.id ? updatedItems : itemList
);
console.log(itemLists);
this.setState({ jumlah: 0, judul: "", itemLists: itemLists });
this.setEditing(false);
this.amount(itemLists); // add this line
};
You can also find the update at the same link

Related

This filter method is working in console but not in ui in react

I want to filter items by filter method and I did it but it doesn't work in UI but
when I log it inside console it's working properly
I don't know where is the problem I put 2 images
Explanation of this code:
Looping inside currencies_info by map method and show them when I click on it and this completely working then I want filter the list when user enter input inside it I use filter method and this completely working in console not in UI
import React, { useState } from "react";
// Artificial object about currencies information
let currencies_info = [
{
id: 1,
name: "بیت کوین (bitcoin)",
icon: "images/crypto-logos/bitcoin.png",
world_price: 39309.13,
website_price: "3000",
balance: 0,
in_tomans: 0
},
{
id: 2,
name: "اتریوم (ethereum)",
icon: "images/crypto-logos/ethereum.png",
world_price: 39309.13,
website_price: "90",
balance: 0,
in_tomans: 0
},
{
id: 3,
name: "تتر (tether)",
icon: "images/crypto-logos/tether.png",
world_price: 39309.13,
website_price: "5",
balance: 0,
in_tomans: 0
},
{
id: 4,
name: "دوج کوین (dogecoin)",
icon: "images/crypto-logos/dogecoin.png",
world_price: 39309.13,
website_price: "1000000",
balance: 0,
in_tomans: 0
},
{
id: 5,
name: "ریپل (ripple)",
icon: "images/crypto-logos/xrp.png",
world_price: 39309.13,
website_price: "1,108",
balance: 0,
in_tomans: 0
}
];
export default function Buy() {
// States
let [api_data, set_api_data] = useState(currencies_info);
const [currency_icon, set_currency_icon] = useState("");
const [currency_name, set_currency_name] = useState("");
const [currency_price, set_currency_price] = useState(0);
const [dropdown, set_drop_down] = useState(false);
let [search_filter, set_search_filter] = useState("");
// States functions
// this function just toggle dropdown list
const toggle_dropdown = () => {
dropdown ? set_drop_down(false) : set_drop_down(true);
};
// this function shows all currencies inside dropdown list and when click on each item replace
// the currency info and hide dropdown list
const fetch_currency = (e) => {
set_drop_down(false);
currencies_info.map((currency) => {
if (e.target.id == currency.id) {
set_currency_name(currency.name);
set_currency_icon(currency.icon);
set_currency_price(currency.website_price);
}
});
};
// this function filter items base on user input value
const filter_currency = (e) => {
set_search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
api_data = set_search_filter;
console.log(api_data);
};
return (
<div className="buy-page-input" onClick={toggle_dropdown}>
{/* currency logo */}
<div className="currency-logo">
<img src={currency_icon} width="30px" />
</div>
{/* currency name in persian */}
<span className="currency-name">{currency_name}</span>
{/* currency dropdown icon */}
<div className="currency-dropdown">
<img className={dropdown ? "toggle-drop-down-icon" : ""}
src="https://img.icons8.com/ios-glyphs/30/000000/chevron-up.png"
/>
</div>
</div>
{/* Drop down list */}
{dropdown ? (
<div className="drop-down-list-container">
{/* Search box */}
<div className="search-box-container">
<input type="search" name="search-bar" id="search-bar"
placeholder="جستجو بر اساس اسم..."
onChange={(e) => {
filter_currency(e);
}}/>
</div>
{api_data.map((currency) => {
return (<div className="drop-down-list" onClick={(e) => {
fetch_currency(e);}} id={currency.id}>
<div class="right-side" id={currency.id}>
<img src={currency.icon} width="20px" id={currency.id} />
<span id={currency.id}>{currency.name}</span>
</div>
<div className="left-side" id={currency.id}>
<span id={currency.id}>قیمت خرید</span>
<span className="buy-price" id={currency.id}>
{currency.website_price}تومان</span>
</div>
</div>);})}
</div>) : ("")});}
Your search_filter looks redundant to me.
Try to change the filter_currency function like this:
const filter_currency = (e) => {
const search = e.target.value;
const filtered = currencies_info.filter((currency) => {
return currency.name.includes(search);
});
set_api_data(filtered);
};
It looks like you are never setting the api_data after you set the filter state.
Change the following
api_data = set_search_filter
console.log(api_data)
to
api_data = set_search_filter
set_api_data(api_data)
However, it then looks like set_search_filter is never used and only set so to improve this further you could remove that state and just have it set the api_data direct. Something like this:
const filter_currency = (e) => {
const search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1
})
set_api_data(search_filter)
}
Change your state value from string to array of the search_filter like this -
let [search_filter, set_search_filter] = useState([]);
and also it should be like this -
const filter_currency = (e) => {
const filterValues = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
set_search_filter(filtervalues);
set_api_data(filterValues);
console.log(filterValues);
};
and use useEffect with search_filter as dependency, so that every time search_filter value is being set, useEffect will trigger re render, for eg:-
useEffect(()=>{
//every time search_filter value will change it will update the dom.
},[search_filter])

Array with inputs in React

Im trying to create a table with an array of products, the problem I have is that the inputs take the value of any input with the same "name". Also, when I try to remove any of the products, it always removes the last one.
https://stackblitz.com/edit/react-gto9bw?file=src/App.js
const [producto, setProducto] = useState({
codigo: '',
nombre: '',
descripcion: '',
precio: '',
cantidad: '',
estado: '',
});
const [productos, setProductos] = useState([]);
const addProducto = () => {
setProductos([...productos, producto]);
};
const removeProducto = (e, index) => {
e.preventDefault();
const list = [...productos];
list.splice(index, 1);
setProductos(list);
};
const handleInputChangeProducto = (e, index) => {
e.preventDefault();
const { name, value } = e.target;
const list = [...productos];
list[index][name] = value;
setProductos(list);
};
The return its a table that has a button to add the product
return (
<div>
<table className="table-size" style={{ border: '1px solid black' }}>
<thead>
<tr>
<th scope="col">Nombre</th>
<th scope="col">Descripcion</th>
</tr>
{productos.map((producto, index) => (
<tr key={index}>
<td>
<input
name="nombre"
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td>
<input
name="descripcion"
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td onClick={(e) => removeProducto(e, index)}>
<Button>Borrar Producto {index}</Button>
</td>
</tr>
))}
</thead>
</table>
<br />
<button onClick={addProducto}>Crear Producto</button>
</div>
I've tried separating the "tr" into a separate component but that doesn't work either.
This is a common issue when using map with index instead of unique key.
So you can try like this:
Please add a global variable key in your case.
let key = 0;
Then set this key in your producto and increase it.
(In my full code, I used codigo as a key field.)
It should always be increased.
On backend API, you should get unique key as well.
Here is a full code.
import React, { useState } from 'react';
import { Button } from 'react-bootstrap';
import './style.css';
let key = 0;
export default function App() {
const [producto, setProducto] = useState({
codigo: '',
nombre: '',
descripcion: '',
precio: '',
cantidad: '',
estado: '',
});
const [productos, setProductos] = useState([]);
const addProducto = () => {
setProductos([...productos, { ...producto, codigo: ++key }]);
};
const removeProducto = (e, index) => {
e.preventDefault();
const list = [...productos];
list.splice(index, 1);
setProductos(list);
};
const handleInputChangeProducto = (e, index) => {
e.preventDefault();
const { name, value } = e.target;
const list = [...productos];
list[index][name] = value;
setProductos(list);
};
console.log({ productos });
return (
<div>
<table className="table-size" style={{ border: '1px solid black' }}>
<thead>
<tr>
<th scope="col">Nombre</th>
<th scope="col">Descripcion</th>
</tr>
{productos.map((producto, index) => (
<tr key={producto.codigo}>
<td>
<input
name="nombre"
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td>
<input
name="descripcion"
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td onClick={(e) => removeProducto(e, index)}>
<Button>Borrar Producto {index}</Button>
</td>
</tr>
))}
</thead>
</table>
<br />
<button onClick={addProducto}>Crear Producto</button>
</div>
);
}
Although the other answers are not wrong - and they all (implicitly) fixed the issues below, I wanted to call out the root cause of the problem you were seeing. It's not the use of indexes as keys (although having unique keys is a good idea for performance reasons).
When you created a new producto (at least in your original code sample), you were doing this:
const addProducto = () => {
setProductos([...productos, producto]);
};
Note the new producto being added to the array is always the same object. That means that your array contains a list of pointers that are all pointing to the same object. Modifying one will modify all of them. That's probably not what you want. Instead, do this:
const addProducto = () => {
setProductos([...productos, {...producto} ]);
};
That spreads the properties of your blank producto object onto a new object and ensures that each object in the array is really a separate thing.
The form's values were not actually controlled by the state. Going from your original code sample, make these changes:
{productos.map((producto, index) => (
<tr key={index}>
<td>
<input
name="nombre"
value={producto.nombre} // <-- this was missing. without it there is no relation between what the user is seeing in the input and what value is stored in the productos array.
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td>
<input
name="descripcion"
value={producto.descripcion} // <-- Same thing here
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
You need an unique key. Try to generate this unique key on your addProducto:
const addProducto = () => {
setProductos([...productos, { ...producto, id: Date.now() }]);
};
And then on your productos.map pass this generated id in your <tr key={producto.id}>.
Its recommended you have "static" indexes as key, React will understand better how manipulate your list and if occurs any modifications in this list, he will not recalculate everything.
Another point, if you need to manipulate something that depends of your previous state, like these add or remove operations, try to use functions for updates, like:
const addProducto = () => {
setProductos(prevProductos => [...prevProductos, { ...producto, id: Date.now() }]);
};
const removeProducto = (e, index) => {
e.preventDefault();
setProductos(prevProductos => [...prevProductos.slice(0, index), ...prevProductos.slice(index + 1)]);
};
Add an id field to your product JSON.
const [producto, setProducto] = useState({
id: '',
codigo: '',
nombre: '',
descripcion: '',
precio: '',
cantidad: '',
estado: '',
});
Update the ID for every product addition,
const addProducto = () => {
setProductos([...productos, {id: (prodcutos.length + 1), ...producto}]);
};
Replace your current key from index to producto.id,
{productos.map((producto, index) => (
<tr key={producto.id}>
Pass this id as param for you handleInputChangeProducto function,
<td>
<input
name="nombre"
onChange={(e) => handleInputChangeProducto(e, producto.id)}
/>
</td>
<td>
<input
name="descripcion"
onChange={(e) => handleInputChangeProducto(e, producto.id)}
/>
</td>
<td onClick={(e) => removeProducto(e, producto.id)}>
<Button>Borrar Producto {index}</Button>
</td>
const addProducto = () => {
setProductos((list) => [
...list,
{
id: list.length, //this should be unique anyway
codigo: '',
nombre: '',
descripcion: '',
precio: '',
cantidad: '',
estado: '',
},
]);
};
const removeProducto = (e, id) => {
setProductos((list) => {
list.splice(id, 1);
return [...list]
});
};
you should change your list in callback manner as shown.

Dynamically add and remove inputs and update nested array

How to add new input fields dynamically to object in nested array in react js when user clicks on plus sign? dynamically add and remove inputs
I want to add and delete propositionTimes dynamically in the handlepropositionTimeAddClick and handlepropositionTimeRemoveClick methods I shared below. How can I do that? And I want to do the same with propositionResponseTimes and analyzers.
const [issue, setIssue] = useState({
firstResponseDuration: "",
firstResponseOvertime: "",
solutionDuration: "",
solutionOvertime: "",
propositionTimes: [{
propositionTime: ""
}],
propositionResponseTimes: [{ propositionResponseTime: "" }],
analyzers: [{ analyzerName: "", analyzerHuaweiId: "" }],
});
const { firstResponseDuration, firstResponseOvertime,solutionDuration, solutionOvertime, propositionTimes, propositionResponseTimes, analyzers } = issue;
.
.
.
// handle click event of the Remove button
const handlepropositionTimeRemoveClick = index => {
};
// handle click event of the Add button
const handlepropositionTimeAddClick = (i) => {
};
.
.
.
{
issue.propositionTimes.map((item, i) => {
return (
<div key={i} className="form-group" >
<label>
Proposition Time
</label>
<TextField
type="datetime-local"
placeholder="Enter propositionTime"
name="propositionTime"
format="dd-MM-yyyy HH:mm"
value={item.propositionTime}
onChange={e => onInputPropositionTimes(e, i)}
/>
<div>
{issue.propositionTimes.length !== 1 && <button
className="mr10"
onClick={() => handlepropositionTimeRemoveClick(i)}>Remove</button>}
{issue.propositionTimes.length - 1 === i && <button onClick={handlepropositionTimeAddClick(i)}>Add</button>}
</div>
</div>
)
})
}
// handle click event of the Remove button
const handlepropositionTimeRemoveClick = index => {
const issueObj = {...issue};
const filteredIssue = issue.propositionTimes.filter((item, ind) => ind !== index);
issueObj.propositionTimes = filteredIssue;
setIssue(issueObj);
};
// handle click event of the Add button
const handlepropositionTimeAddClick = (i) => {
const issueObj = {...issue};
const newObj = {
propositionTime: "" // Code to add new propositionTime
}
issueObj.propositionTimes.push(newObj)
setIssue(issueObj);
};
Also in your handlepropositionTimeAddClick function, don't call it directly. Just pass the reference
{issue.propositionTimes.length - 1 === i && <button onClick={() => handlepropositionTimeAddClick(i)}>Add</button>}

JSON Array - update setState for unilimited number of JSONs in array. React

I have a state where I store unlimited JSON objects.
example:
const [creditParams, setCreditParams] = useState([
{
amount: "",
installment: "",
insurance: "",
commission: "",
numOfInstallments: "",
},
{
amount: "",
installment: "",
insurance: "",
commission: "",
numOfInstallments: "",
},
]);
Users can add another object (credit calculator) by clicking on the button, and the view is generated by mapping that example array.
The data is load from input text fields
<TextField
id="installment"
type="number"
label="Installment amount:"
value={installment}
onChange={handleChange}
helperText="How big is your installment"
variant="outlined"
index={index}
/>
I can't find a reasonable way to handle input here.
When I have only one object I use
const handleChange = (e) => {
console.log(e);
const name = e.target.id;
const value = e.target.value;
setCreditParams({ ...creditParams, [name]: value });
};
now I try
const handleChange = (e) => {
console.log(e);
const name = e.target.id;
const value = e.target.value;
const index = e.target.index;
let creditObject = creditParams[index];
creditObject = { ...creditObject, [name]: value };
creditParams[index] = creditObject;
setCreditParams(creditParams);
};
But that doesn't update input in text fields :(
Can you help, please?
That part creditParams[index] = creditObject; is incorrect. You shouldn't modify state variable directly. Instead use map to find and change the right item:
setCreditParams(prevCreditParams => prevCreditParams.map(
(item, itemIndex) => itemIndex === index ? creditObject : item
));

Why button Enable Condition after clicking radio button is not working?

When I try to enable delete button it doesn't work although I put logic for that.. In fact after putting the following the Delete button is always enable. Can't find the problem.
Here's the code:
Sports.js
// initiallising states
const [items1, setItems1] = useState({
pageCount: '',
field_names_state: [],
toggle: false,
elements: [],
data_form: [],
sports_id: '',
buttonDisable: "0",
});
const onRadioChange = (e) => {
items1.sports_id = e.target.value;
items1.buttonDisable="1";
alert(items1.buttonDisable);
};
// mapping our fetched data and send them to Table_Sports.js
const data1 = items1.elements.map((item) => (
<Table_Sports key={item.sports_id} item={item} action={onRadioChange} />
));
<button type='button' disabled={items1.buttonDisable=="1"} onClick={(e) => handleDelete(e)}>
Delete
</button>
Table_Sports.js
<td>
<input
type='radio'
// defaultValue={props.item.sports_id}
defaultValue={props.}
name='sports_id'
onClick={(e) => props.action(e)}
/>
</td>
<td>
<input
type='text'
defaultValue={props.item.sports_name}
name='sports_name'
contentEditable='true'
/>
</td>
To change a value at the state, you should call setItems1, passing the new state object as a parameter instead of manipulate it's properties directly. If you change your onRadioChange function like this it should work:
const onRadioChange = (e) => {
setItems1({
...items1,
sports_id: e.target.value,
buttonDisable: "1",
});
};

Categories