How to protect an array from being refreshed - javascript

I have clothes and orders tables and array which based on Clothes and Orders models.Whenever I push a clothes element into Orders array and especially to update amount of clothes and price which selected,Clothes array also being updated as well and I don't want it.I want to keep my array as immutable.I searched for it on the internet but didn't work.Here's what I tried below.Also to make it clear I'll add pictures here
https://imge.to/i/vg2aYm
https://imge.to/i/vg2uvF
HTML
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Clothes</th>
<th scope="col">Price</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let i of clothesList;let a = index">
<td>{{i.name}}</td>
<td>{{i.price}}$</td>
<td><button class="btn btn-alert" (click)="onAddItem(i)" >Add To Cart</button></td>
</tr>
</tbody>
</table>
<table class="table" *ngIf="orders.length>0">
<thead>
<tr>
<th scope="col">Clothes</th>
<th scope="col">Amount</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let order of orders;">
<td>{{order.name}}</td>
<td>{{order.amount}}</td>
<td>{{order.price}}</td>
</tr>
</tbody>
<hr>
<strong>Total Cost: {{totalCost}}</strong>
</table>
TS
export class AppComponent {
private clothesList:Clothes[]=[
new Clothes(1,'Hat',500,1),
new Clothes(2,'Shoes',150,1),
new Clothes(3,'Pants',100,1),
new Clothes(4,'Jacket',200,1),
new Clothes(5,'T-Shirt',120,1),
new Clothes(6,'Souvether',150,1),
new Clothes(7,'Scarf',400,1)
];
private orders:Order[]=[];
onAddItem(value)
{
if(this.orders.find(i => i.name===value.name))
{
let myIndex= this.orders.indexOf(value);
value.amount++;
this.orders[myIndex].price+=this.orders[myIndex].price;
}
else
{
this.orders.push(value);
}
}
}

This is because the elements inside both the clothes and order array share same reference, You need to deep clone your object to break the reference:
Try the following:
onAddItem(value){
let order = this.orders.find(i => i.name === value.name);
if (order) {
value.amount++;
order.price *= 2;
}
else {
this.orders.push(JSON.parse(JSON.stringify(value))); // break the reference
}
}

try
this.orders.push(angular.copy(value));
this will add a copy of the object to the orders list an not a reference of it

As mentioned by others, the Clothes object you're passing in to onAddItem is a reference to the corresponding Clothes object in clothesList, so when you mutate that object, it will mutate the original object.
If Clothes is a simple class, you can just use the spread operator to make a copy:
onAddItem(value) {
let copyOfValue = {...value};
...
}
You could also use the Clothes constructor to make a copy:
onAddItem(value) {
let copyOfValue = new Clothes(value.someProperty, value.anotherProperty, value.aThirdProperty, value.aFourthProperty);
...
}

Related

*ngFor is showing an error of undefined when trying to loop over an object in angular

I get the below error when trying to loop over an object using *ngFor directive in angular:
Type 'Inventory' is not assignable to type 'NgIterable | null | undefined'.
18 <tr *ngFor="let item of searchedInventory;">
but the searchedInventory has data in it when I console logged it -
{id: 1, foodName: 'idli', foodDescription: 'made from rice', date: '2023-02-07 16:14:37.793398+05:30', price: 30, …}
The component.ts file as below,
searchedInventory!: Inventory;
constructor(private inventoryDataService : InventoryDataService, private route : ActivatedRoute){
}
ngOnInit(): void {
this.foodname = this.route.snapshot.params['foodname'];
this.searchedItems();
// console.log(this.foodname);
}
foodname!: String;
searchedItems(){
this.inventoryDataService.retrieveFoodByName(this.foodname).subscribe(
response => {
// console.log(response);
this.searchedInventory = response;
console.log(this.searchedInventory);
}
)
}
my HTML page is,
<h1>List of foods:</h1>
<div class="container">
<table class="table">
<thead>
<tr>
<th>id</th>
<th>Food Name</th>
<th>Food Description</th>
<th>Price</th>
<th>Date</th>
<th>Hotel Name</th>
<th>Hotel Address</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of searchedInventory;">
<td>{{item.id}}</td>
<td>{{item.foodName}}</td>
<td>{{item.foodDescription}}</td>
<td>{{item.price}}</td>
<td>{{item.date}}</td>
<td>{{item.hotelName}}</td>
<td>{{item.hotelAddress}}</td>
<td><button class="btn btn-success">Select</button></td>
</tr>
</tbody>
</table>
</div>
Editted,
I tried to convert the object into an array,
this.values = Object.values(this.searchedInventory);
console.log(this.values);
}
and the html is,
<tr *ngFor="let item of values">
<td id="values">{{item}}</td>
<td><button class="btn btn-success">Select</button></td>
</tr>
now the the array is not printing in row but as a column.
data is coming as an object don't need to iterate it
<tr>
<td>{{searchedInventory.id}}</td>
<td>{{searchedInventory.foodName}}</td>
<td>{{searchedInventory.foodDescription}}</td>
<td>{{searchedInventory.price}}</td>
<td>{{searchedInventory.date}}</td>
<td>{{searchedInventory.hotelName}}</td>
<td>{{searchedInventory.hotelAddress}}</td>
<td><button class="btn btn-success">Select</button></td>
</tr>
I solved it on my own!!!
Actually the problem was in the backend Springboot,
#GetMapping(path="/admin/inventory/search/{food_name}")
public Inventory getFoodByName(#PathVariable String food_name) {
Inventory foodname = inventoryService.findByFoodName(food_name);
if(foodname == null) {
throw new TodoNotFoundException("foodname - " + food_name);
}
return foodname;
}
to,
#GetMapping(path="/admin/inventory/search/{food_name}")
public List<Inventory> getFoodByName(#PathVariable String food_name) {
Inventory foodname = inventoryService.findByFoodName(food_name);
if(foodname == null) {
throw new TodoNotFoundException("foodname - " + food_name);
}
List items = new ArrayList<>();
items.add(foodname);
return items;
}
so now my backend return a ArrayList, with which I can iterate using *ngFor and it is executing perfectly!!!
sometimes I need to look a deep into everything to figure out the solution. I was thinking the problem was with the *ngFor!

How to search/find a words/data (in this case "Country ") from a table ( api data) with a search box?

I want to search country names with that text box, find the specific data from that massive list of names, and show that the only data to the user, not all the data. Please help me to do that. I have no idea how to do this.
// api section
const tbody = document.querySelector('#tbody');
const getdata = async () => {
const endpoint = "https://api.covid19api.com/summary",
response = await fetch(endpoint),
data = await response.json(),
Countries = data.Countries;
Countries.forEach(countryObj => {
let { Country, NewConfirmed, TotalConfirmed, NewDeaths, TotalDeaths, NewRecovered, TotalRecovered, Date } = countryObj;
tbody.innerHTML += `<tr>
<td>${Country}</td>
<td>${NewConfirmed}</td>
<td>${TotalConfirmed}</td>
<td>${NewDeaths}</td>
<td>${TotalDeaths}</td>
<td>${NewRecovered}</td>
<td>${TotalRecovered}</td>
<td>${Date}</td>
</tr>`;
});
}
getdata();
<---------------------------- search box function------------->
// Don't know how to do it.....help me ....Thanks in advance :)
<!--------------search Box & search button ------- -->
<input type="text" id="myInput" placeholder=" Search Country " >
<input type="submit" value="Search" class="submit">
<!----------------data table--------------- -->
<table class="table">
<thead>
<tr>
<th scope="col">Country</th>
<th scope="col">NewConfirmed</th>
<th scope="col">TotalConfirmed</th>
<th scope="col">NewDeaths</th>
<th scope="col">TotalDeaths</th>
<th scope="col">NewRecovered</th>
<th scope="col">TotalRecovered</th>
<th scope="col">Last Updated on</th>
</tr>
</thead>
<tbody id="tbody">
</tbody>
</table>
I want to search country names with that text box, find the specific data from that massive list of names, and show that the only data to the user, not all the data. Please help me to do that.
The only thing that is actually needed here is a way to 'search' the data and display those results. This can be done using the filter() method for an array.
Essentially, you just need to store your data in a global variable that can be filtered later, based on user input. Also, I usually make it a point to separate certain functionality, like displaying data. So instead of displaying the country data inside of the getData() function, I would create a separate function that just filters and displays data. This way you can call it after you fetch the data, and then call that same function each time you search (rather than have repeated code that displays countries in the table).
let countriesData = [];
const getdata = async () => {
const endpoint = "https://api.covid19api.com/summary",
response = await fetch(endpoint),
data = await response.json();
countriesData = data.Countries;
_DisplayCountries();
}
const _DisplayCountries = (c = "") => {
let tbody = document.querySelector("#tbody");
tbody.innerHTML = ``;
countriesData.filter(country => country.Country.toLowerCase().includes(c.toLowerCase())).forEach(result => {
tbody.innerHTML += `<tr>
<td>${result.Country}</td>
<td>${result.NewConfirmed}</td>
<td>${result.TotalConfirmed}</td>
<td>${result.NewDeaths}</td>
<td>${result.TotalDeaths}</td>
<td>${result.NewRecovered}</td>
<td>${result.TotalRecovered}</td>
<td>${result.Date}</td>
</tr>`;
});
}
getdata();
document.querySelector("#mySubmit").addEventListener("click", e => {
_DisplayCountries(document.querySelector("#myInput").value);
});
<input type="text" id="myInput" placeholder=" Search Country ">
<input type="submit" id="mySubmit" value="Search" class="submit">
<table class="table">
<thead>
<tr>
<th scope="col">Country</th>
<th scope="col">NewConfirmed</th>
<th scope="col">TotalConfirmed</th>
<th scope="col">NewDeaths</th>
<th scope="col">TotalDeaths</th>
<th scope="col">NewRecovered</th>
<th scope="col">TotalRecovered</th>
<th scope="col">Last Updated on</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
NOTES
There are definitely a lot of different ways to filter() the data and I opted for a very simple includes() method. I also added toLowerCase() to the country and the search string to make it case-insensitive, but I understand there are several ways to do this as well.
let regex = new RegExp(c, "i");
countriesData.filter(country => country.Country.match(regex))
That for example would also return a list of search results that are case-insensitive.

How do I create a table from a JSON object in React JS?

What I want to do is use Propublica's nonprofit API to pull in information about various nonprofits and display certain attributes in a table.
Right now, I'm fetching an object:
const [ orgs, setOrgs ] = useState({})
const fetchOrgs = async () => {
const result = await Axios.get(`${API_URL}?state%5Bid%5D=${query}`)
setOrgs(result.data.organizations)
}
According to their API, organization objects are like this:
{
"organization":{
"id":142007220,
"ein":142007220,
"name":"PRO PUBLICA INC",
"careofname":null,
"address":"155 AVE AMERICA 13 FL",
"city":"NEW YORK",
"state":"NY",
"zipcode":"10013-0000",
"exemption_number":0,
"subsection_code":3,
"affiliation_code":3,
"classification_codes":"1000",
"ruling_date":"2008-02-01",
"deductibility_code":1,
"foundation_code":15,
"activity_codes":"0",
"organization_code":1,
"exempt_organization_status_code":1,
"tax_period":"2018-12-01",
"asset_code":8,
"income_code":8,
"filing_requirement_code":1,
"pf_filing_requirement_code":0,
"accounting_period":12,
"asset_amount":40988939,
"income_amount":27237842,
"revenue_amount":26685933,
"ntee_code":"A20",
"sort_name":null,
"created_at":"2020-04-13T21:42:55.607Z",
"updated_at":"2020-04-13T21:42:55.607Z",
"data_source":null,
"have_extracts":null,
"have_pdfs":null
},
This is my current function, which is not working:
const displayTable = () => {
return (
<Table striped border='true' hover='true' responsive='true'>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>State</th>
<th>City</th>
<th>NTEE</th>
<th>Income</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Test</td>
<td>Test</td>
<td>Test</td>
<td>Test</td>
<td><a href=' '>Link</a></td>
</tr>
{Object.keys(orgs).map(({name, state, ntee_code, income_amount}, i) => (
<tr key={i}>
<td>{i + 1}</td>
<td>{name}</td>
<td>{state}</td>
<td>{ntee_code}</td>
<td>{income_amount}</td>
</tr>
))}
</tbody>
</Table>
)
}
When I run this, I get the test row, and then many rows of empty cells. Only the "#" column has anything, since it's just the index value. What am I doing wrong here? Any help would be much appreciated, I've only just started learning React and JS.
here is a working example:
return (
<div>
<h1>Organizations:</h1>
<table style={{ width: '100%', textAlign: 'center', border: 'solid 1px' }}>
<thead>
<th>Index</th><th>Name</th><th>State</th><th>ntee_code</th><th>income_amount</th>
</thead>
<tbody>
{orgs.map((organization, i) => {
const org = organization.organization;
return (
<tr>
<td>{i}</td><td>{org.name}</td><td>{org.state}</td><td>{org.ntee_code}</td><td>{org.income_amount}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
I'm posting just the returned markup as I understood you have no problem with fetching the data, and you just don't know how to display.
Your solution had the right idea of iterating over the organization array to create <tr>s. But you didn't need the Object.keys() to access the values. You first need to iterate over the objects in the array, and then drill down each object using the . syntax: const org = organization.organization; and then e.g org.name.
Note that each of your objects actually has only one field, i.e organization, so you first need to drill into it and only then you can get the values further down the object tree. If you want to deconstruct the object in map() you can, and again you had the right idea about how it's done, you just got the layer wrong. So you can do
{orgs.map(({organization}, i) => {
const { name, state, ntee_code, income_amount } = organization;
return (
<tr>
<td>{i}</td><td>{name}</td><td>{state}</td><td>{ntee_code}</td><td>{income_amount}</td>
</tr>
)
})}
I hope it can help and it's all clear.
Good luck!

How to force updating a value which has been put to array as a text for an element?

I'd like to create a timer for questions which I get from database. User will need to answer them in 5 minutes. Timer structure is in the Timer class which I placed in the separate file.
<table>
<tr>
<th>ID</th>
<th>Type</th>
<th>Q</th>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
<th>Timer</th>
</tr>
{this.state.unaccQst}
</table>
<form action="" onSubmit={e => {
e.preventDefault()
...
questionCollector(newForm).then( //get questions from database
data => {
let qstDetArr = []
for(let qstDetails of data){
...
var timer1 = new Timer(5,0) //set a 5 minute timer for each of the questions
var timer2 = setInterval(() => {
let getTime = timer1.getTime() //check the current time
if(getTime.minutes == 0 && getTime.seconds == 0) {
console.log("DELETED")
clearInterval(timer2)
}
else this.setState({[`time_${qstDetails["_id"]}`]: `${getTime.minutes}:${getTime.seconds}`}) //set the current time to a state
console.log(this.state[`time_${qstDetails["_id"]}`]) //this console.log works perfectly (like 4:57, 4:56, 4:55, etc...)
}, 1000)
qstDetArr.push(<tr> //push a table row to an array
<td>{qstDetails["_id"]}</td>
<td>{qstDetails.type}</td>
<td>{qstDetails.translations.en.Q}</td>
<td>{qstDetails.translations.en.A}</td>
<td>{qstDetails.translations.en.B}</td>
<td>{qstDetails.translations.en.C}</td>
<td>{qstDetails.translations.en.D}</td>
<td>{this.state[`time_${qstDetails["_id"]}`]}</td> //this line should update every time the state will update, but it doesn't
</tr>)}
this.setState({unaccQst: [...this.state.unaccQst, ...qstDetArr]})
},
(err) => {}
)
}}>
Everything works fine besides the line:
<td>{this.state[`time_${qstDetails["_id"]}`]}</td>
which value stucked. It's the same as it was when I put it to an array.
There needs to be a way to update it, but it doesn't work even when i want to update it with more polite state (like just i++)
So in my case I managed to do it simply by putting into a state only the data I needed to construct tr with and do it later using map():
<table>
<tr>
<th>ID</th>
<th>Type</th>
<th>Q</th>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
<th>Timer</th>
</tr>
{this.state.unaccQst.map(qst => {
return <tr>
<td>{qst["_id"]}</td>
<td>{qst.type}</td>
<td>{qst.translations.en.Q}</td>
<td>{qst.translations.en.A}</td>
<td>{qst.translations.en.B}</td>
<td>{qst.translations.en.C}</td>
<td>{qst.translations.en.D}</td>
<td>{this.state[`time_${qst["_id"]}`]}</td>
</tr>
})}
</table>

vuetify simple table. Convert nested object propertied to <td> columns

I am using vuetify 2.1 and a simple nested table. with the following data structure in my data model:
groups:[
{
style:"X",
colours:"colours",
sizes:"standard",
marketplaces:[
{
markeplace:"UK",
pricelists:["A","B","C"]
},
{
markeplace:"EU",
pricelists:["D","E","F"]
},
{
markeplace:"ROW",
pricelists:["G","H","I"]
},
]
},
{
style:"X",
colours:"Black/White",
sizes:"standard",
marketplaces:[
{
markeplace:"UK",
pricelists:["X","Y","Z"]
},
{
markeplace:"EU",
pricelists:["P","Q","R"]
},
{
markeplace:"ROW",
pricelists:["S","T","U"]
},
]
}
]
What I want to achieve is < td > records for:
style
colour
size
UK.pricelists[0]
UK.pricelists[1]
UK.pricelists[2]
EU.pricelists[0]
EU.pricelists[1]
EU.pricelists[2]
ROW.pricelists[0]
ROW.pricelists[1]
ROW.pricelists[2]
<v-simple-table
dense
calculate-widths
fixed-header
height="90vh"
>
<template v-slot:default>
<thead>
<tr>
<th>Style</th>
<th>Colour Group</th>
<th>Size Group</th>
<th>UK 1</th>
<th>UK 2</th>
<th>UK 3</th>
<th>EU 1</th>
<th>EU 2</th>
<th>EU 3</th>
<th>ROW 1</th>
<th>ROW 2</th>
<th>ROW 3</th>
</tr>
</thead>
<tbody>
<tr v-for="group in groups" >
<td>{{group.style}}</td>
<td>{{group.colour}}</td>
<td>{{group.size}}</td>
<!-- this is where I am struggling... I need the next 9 td records to iterate through two levels of arrays. -->
<td v-for="mkt in group.marketplaces">{{mkt.pricelists[0]}}<td>
</tr>
</tbody>
</template>
</v-simple-table>
for reference I have complete control over the API and the shape of the data object so feel free to suggest an alternative document structure. Can you native iterate over multiple levels in vuetify simple table - perhaps using array.foreach().
Is there a vue equivalent of react-fragment which acts as outer nesting element but does not actually render anything. The challenge is that this is within a table row and I need a collection around only some of the cells in the row.
do I move the logic to a method which remaps the pricelists for the passed in group. In my situation, all groups will have the same marketplaces in the same order and each marketplace will have the same number of price lists so I don't have any issues with sorting or padding the array.
In the absence of any other suggestions, I have create a method to remap the data into a single array:
methods: {
remapPricelists(style,colours,sizes){
/* This should find a single match */
let group = this.groups.filter(g=>{
return g.style == style
&& g.colours == colours
&& g.sizes == sizes
});
let pl =[];
group[0].pricelists.map(plst =>{
pl = pl.concat(plst.pricelists);
});
return pl;
}
}
DISCLAIMER: I have edited the above code from my live data which has a slightly different format (more outer groups and differently named fields) so E&OE. In production, I will likely abstract the group fetch to a separate method as I am going to need it in lots of places and will likely strip the outer array to just leave the group object so that I can access the inner data without having to specify the group array-index.

Categories