I am trying to implement an increment button in React that will update the value of a field in a table by one. Specifically, I am trying to increment the "stock" by one
This is what I'm currently trying:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cars: [
{
"manufacturer": "Toyota",
"model": "Rav4",
"year": 2008,
"stock": 3,
"price": 8500
},
{
"manufacturer": "Toyota",
"model": "Camry",
"year": 2009,
"stock": 2,
"price": 6500
},
{
"manufacturer": "Toyota",
"model": "Tacoma",
"year": 2016,
"stock": 1,
"price": 22000
},
{
"manufacturer": "BMW",
"model": "i3",
"year": 2012,
"stock": 5,
"price": 12000
},
{
"manufacturer": "Chevy",
"model": "Malibu",
"year": 2015,
"stock": 2,
"price": 10000
},
{
"manufacturer": "Honda",
"model": "Accord",
"year": 2013,
"stock": 1,
"price": 9000
},
{
"manufacturer": "Hyundai",
"model": "Elantra",
"year": 2013,
"stock": 2,
"price": 7000
},
{
"manufacturer": "Chevy",
"model": "Cruze",
"year": 2012,
"stock": 2,
"price": 5500
},
{
"manufacturer": "Dodge",
"model": "Charger",
"year": 2013,
"stock": 2,
"price": 16000
},
{
"manufacturer": "Ford",
"model": "Mustang",
"year": 2009,
"stock": 1,
"price": 8000
},
]
};
}
onHeaderClick(){
}
increaseStock(event){
this.setState({cars: this.state.cars.stock + 1})
}
decreaseStock(event){
this.setState({cars: this.state.cars.stock + 1})
}
render() {
return (
<table>
<tr>
<th>Manufacturer</th>
<th>Model</th>
<th onClick={()=> this.onHeaderClick}>Year</th>
<th>Stock</th>
<th>Price</th>
<th>Options</th>
<th></th>
</tr>
{
this.state.cars.map(car => (
<tr key={car.model}>
<td>{car.manufacturer}</td>
<td>{car.model}</td>
<td>{car.year}</td>
<td>{car.stock}</td>
<td>${car.price}.00</td>
<td><button type="button" onClick={this.increaseStock.bind(this)}>Increment</button></td>
<td><button type="button" onClick={this.decreaseStock.bind(this)}>Decrement</button></td>
</tr>
))
}
</table>
);
};
}
ReactDOM.render(<App />, document.getElementById("app"))
I have tried a bunch of different implementations all to no avail. Where am I going wrong?
You are trying to update the whole array cars (which will end up being just a number) and not the specific car object.
What you should do it pass the current car as parameter at your function and update the state accordingly
<td><button type="button" onClick={() => increaseStock(car)}>Increment</button></td>
method
increaseStock = car => {
const index = this.state.cars.findIndex(cr => cr.model === car.model)
let newCar = { ...this.state.cars[index] }
newCar.stock += 1
this.setState({ ...this.state, cars: Object.assign([ ...this.state.cars ], { [index]: newCar }) })
}
TLDR;
You should update the stock value for a specific car from the array, not the cars array itself. This one seems to work.
Suggestion:
As suggested in the official ReactJS documentation try to breakdown into smaller components.
In your case, the <tr> from the map function can easily be extracted as a separate component (e.g. CarModelItem). Or even deeper to extract the buttons in a separate component. After splitting that you can just pass the increaseStock and increaseStock as callbacks to those components props.
To start, you need to add a unique id for each element
cars: [{
id: 1,
manufacturer: "Toyota",
model: "Rav4",
year: 2008,
stock: 3,
price: 8500
},
{
id: 2,
//...
}]
And now, you have to find the element with its id in your array then increment or decrement stock
increment(id) {
let items = [...this.state.cars];
const index = items.map(item => item.id).indexOf(id);
let item = {...this.state.cars[index]};
item.stock = item.stock + 1;
items[index] = item;
this.setState({items});
}
decrement(id) {
let items = [...this.state.cars];
const index = items.map(item => item.id).indexOf(id);
let item = {...this.state.cars[index]};
item.stock = item.stock - 1; // maybe you need to check if stock = 0
items[index] = item;
this.setState({items});
}
Since you have an array of cars in state you need to find a way to identify which model is meant to be updated. In this example I've attached a data attribute to the button which can be picked up in functions.
In the class methods make a copy of the cars state, find the index of the car that you want to update, increase/decrease its stock value, and then update the state.
Note: it's better to bind your class methods in the constructor.
const { Component } = React;
class Example extends Component {
constructor(props) {
super(props);
this.state={cars:[{manufacturer:"Toyota",model:"Rav4",year:2008,stock:3,price:8500},{manufacturer:"Toyota",model:"Camry",year:2009,stock:2,price:6500},{manufacturer:"Toyota",model:"Tacoma",year:2016,stock:1,price:22e3},{manufacturer:"BMW",model:"i3",year:2012,stock:5,price:12e3},{manufacturer:"Chevy",model:"Malibu",year:2015,stock:2,price:1e4},{manufacturer:"Honda",model:"Accord",year:2013,stock:1,price:9e3},{manufacturer:"Hyundai",model:"Elantra",year:2013,stock:2,price:7e3},{manufacturer:"Chevy",model:"Cruze",year:2012,stock:2,price:5500},{manufacturer:"Dodge",model:"Charger",year:2013,stock:2,price:16e3},{manufacturer:"Ford",model:"Mustang",year:2009,stock:1,price:8e3}]};
this.increaseStock = this.increaseStock.bind(this);
this.decreaseStock = this.decreaseStock.bind(this);
}
increaseStock(event) {
const { model } = event.target.dataset;
const cars = [...this.state.cars];
const index = cars.findIndex(car => car.model === model);
if (index >= 0) {
++cars[index].stock;
this.setState({ cars });
}
}
decreaseStock(event) {
const { model } = event.target.dataset;
const cars = [...this.state.cars];
const index = cars.findIndex(car => car.model === model);
if (index >= 0) {
--cars[index].stock;
this.setState({ cars });
}
}
render() {
return (
<table>
<tr>
<th>Manufacturer</th>
<th>Model</th>
<th onClick={()=> this.onHeaderClick}>Year</th>
<th>Stock</th>
<th>Price</th>
<th>Options</th>
<th></th>
</tr>
{
this.state.cars.map(car => (
<tr key={car.model}>
<td>{car.manufacturer}</td>
<td>{car.model}</td>
<td>{car.year}</td>
<td>{car.stock}</td>
<td>${car.price}.00</td>
<td>
<button
data-model={car.model}
type="button"
onClick={this.increaseStock}
>Increment
</button>
</td>
<td>
<button
data-model={car.model}
type="button"
onClick={this.decreaseStock}
>Decrement
</button>
</td>
</tr>
))
}
</table>
);
};
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Related
I have the following array
Array["MyArray",
{
"isLoaded":true,
"items":
[{
"id":"4",
"name":"ProductA",
"manufacturer":"BrandA",
"quantity":1,
"price":"25"
},{
"id":"1",
"name":"ProductB",
"manufacturer":"BrandB",
"quantity":5,
"price":"20"
}],
"coupons":null
}
]
I need to load product names and their quantity from the array.
const result = [key, value].map((item) => `${item.name} x ${item.quantity}`);
Here's one possible way to achieve the desired result:
const getProductsAndQuantity = ([k , v] = arr) => (
v.items.map(it => `${it.name} x ${it.quantity}`)
);
How to use it within the context of the question?
localforage.iterate(function(value, key, iterationNumber) {
console.log([key, value]);
const val2 = JSON.parse(value);
if (val2 && val2.items && val2.items.length > 0) {
console.log(val2.items.map(it => `${it.name} x ${it.quantity}`).join(', '))
};
});
How it works?
Among the parameters listed in the question ie, value, key, iterationNumber, only value is required.
The above method accepts the key-value pair as an array (of 2 elements) closely matching the console.log([key, value]); in the question
It uses only v (which is an object). On v, it accesses the prop named items and this items is an Array.
Next, .map is used to iterate through the Array and return each product's name and quantity in the desired/expected format.
Test it out on code-snippet:
const arr = [
"MyArray",
{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}
];
const getProductsAndQuantity = ([k, v] = arr) => (
v.items.map(
it => `${it.name} x ${it.quantity}`
)
);
console.log(getProductsAndQuantity());
I understood. You should learn about array methods such as map, filter, reduce. Here you go...
const items = [{
"id":"4",
"name":"ProductA",
"manufacturer":"BrandA",
"quantity":1,
"price":"25"
},{
"id":"1",
"name":"ProductB",
"manufacturer":"BrandB",
"quantity":5,
"price":"20"
}];
const result = items.map((item) => `${item.name} x ${item.quantity}`);
console.log(result);
I think I understand the question to say that the input is an array of objects, each containing an array of items. The key is that a nested array requires a nested loop. So, we iterate the objects and their internal items (see the lines commented //outer loop and // inner loop below)
Also, half-guessing from the context, it looks like the that the OP aims to assemble a sort of invoice for each object. First a demo of that, (and see below for the version simplified to exactly what the OP asks)...
const addInvoice = obj => {
let total = 0;
// inner loop
obj.invoice = obj.items.map(i => {
let subtotal = i.quantity * i.price;
total += subtotal
return `name: ${i.name}, qty: ${i.quantity}, unit price: ${i.price}, subtotal: ${subtotal}`
});
obj.invoice.push(`invoice total: ${total}`);
}
const objects = [{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}]
// outer loop
objects.forEach(addInvoice);
console.log(objects);
If my guess about the goal went to far, just remove the unit price, subtotal and total lines from the invoice function...
const objects = [{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}]
const summaryString = obj => {
return obj.items.map(i => `${i.name}, ${i.quantity}`);
}
const strings = objects.map(summaryString);
console.log(strings);
28 | <Grid.Row>
29 | {cars.map((car) => (
30 | <Grid.Column style={{marginBottom:"1em"}}>
> 31 | <CarComponent car={car} imagePath={carImages.filter(image=>image.car.id===car.id)[0].imagePath}></CarComponent>
| ^ 32 |
33 | </Grid.Column>
34 | ))}
I get the information (brand, color, description etc.) and photos of the vehicles from separate services (carController, carImageController). As such, when trying to list the cars, I choose the photo with the carId equal to the id of that car in the list of photos for each car in the car list. I write the imagePath as carImages.filter(image=>image.car.id===car.id)[0].imagePath} to do this check on the data coming from my photo service and send it as props to the car component. Sometimes I get this typeError when everything is working fine. Why might it be caused?
Car Function Component:
export default function CarComponent({ car, imagePath}) {
return (
<div>
<Card style={{height:"388px"}}>
<Image
src={imagePath}
wrapped
ui="false"
style={{height:"200px"}}
/>
<Card.Content>
<Card.Header>{car.brand.name}</Card.Header>
<Card.Meta>
<span className="date">{car.modelYear}</span>
</Card.Meta>
<Card.Description>{car.description}</Card.Description>
</Card.Content>
<Card.Content extra style={{color:"black"}} >
<h3 >{car.dailyPrice} ₺</h3>
<Button secondary animated>
<Button.Content visible>Kirala</Button.Content>
<Button.Content hidden>
<Icon name="arrow right" />
</Button.Content>
</Button>
</Card.Content>
</Card>
</div>
);}
Car Image Class
export default class CarImageService{
getCarImages(){
return axios.get("http://localhost:8080/api/images/getAll")
}
}
CarsList Page
export default function CarsList() {
const [cars, setCars] = useState([]);
const [carImages, setCarImage] = useState({});
useEffect(() => {
let carImageService = new CarImageService();
carImageService
.getCarImages()
.then((result) => setCarImage(result.data.data));
}, []);
useEffect(() => {
let carService = new CarService();
carService.getCars().then((result) => setCars(result.data.data));
}, []);
return (
<div>
<Grid columns={3} >
<Grid.Row>
{cars.map((car) => (
<Grid.Column style={{marginBottom:"1em"}}>
<CarComponent car={car} imagePath={carImages.filter(image=>image.car.id===car.id)[0].imagePath}></CarComponent>
</Grid.Column>
))}
</Grid.Row>
</Grid>
</div>
);
}
Get All Car Images Request URL: http://localhost:8080/api/images/getAll
Get All Car Images Response Body:
{
"success": true,
"message": null,
"data": [
{
"id": 26,
"imagePath": "http://res.cloudinary.com/dp39jsge0/image/upload/v1629406293/hx80jyfrus88tar0psq4.png",
"createdAt": "2021-08-19",
"car": {
"id": 1,
"modelYear": 2017,
"dailyPrice": 600,
"description": "A6 2.0TDI QUATTRO EDITION",
"brand": {
"id": 2,
"name": "Audi"
},
"color": {
"id": 8,
"name": "Beyaz"
},
"busy": false
}
},
{
"id": 27,
"imagePath": "http://res.cloudinary.com/dp39jsge0/image/upload/v1629406541/dynoc7dnjcbns0mv2y1m.png",
"createdAt": "2021-08-19",
"car": {
"id": 2,
"modelYear": 2018,
"dailyPrice": 400,
"description": "ALFA ROMEO GIULIETTA 1.6 JTD PROGRESSİON 120 HP",
"brand": {
"id": 1,
"name": "Alfa Romeo"
},
"color": {
"id": 9,
"name": "Gri"
},
"busy": false
}
},
{
"id": 28,
"imagePath": "http://res.cloudinary.com/dp39jsge0/image/upload/v1629406744/fmvdbmqaennil4ptdyoc.png",
"createdAt": "2021-08-19",
"car": {
"id": 3,
"modelYear": 2018,
"dailyPrice": 550,
"description": "BMW 320 DİZEL OTOMATİK-EDITION M SPORT",
"brand": {
"id": 3,
"name": "BMW"
},
"color": {
"id": 10,
"name": "Kırmızı"
},
"busy": false
}
},
{
"id": 29,
"imagePath": "http://res.cloudinary.com/dp39jsge0/image/upload/v1629406930/dq4htj3rrdjlbpiqa8iq.png",
"createdAt": "2021-08-20",
"car": {
"id": 4,
"modelYear": 2016,
"dailyPrice": 700,
"description": "C180 COUPE",
"brand": {
"id": 10,
"name": "Mercedes - Benz"
},
"color": {
"id": 11,
"name": "Lacivert"
},
"busy": false
}
}
]
}
Refresh page a few times:sample of list of cars
The initial value for carImages must be array, not object.
const [carImages, setCarImage] = useState([]);
Notice the initial value: [], not {}
For the second error:
You need to check the result of filter because filter returns an empty array when getCars() returns before getCarImages() or if some cars don't have any images. Plus, to get rid of the array, you need to use find instead of filter because you expect a single result.
export default function CarsList() {
const [cars, setCars] = useState([]);
const [carImages, setCarImage] = useState([]);
useEffect(() => {
let carImageService = new CarImageService();
carImageService
.getCarImages()
.then((result) => setCarImage(result.data.data));
}, []);
useEffect(() => {
let carService = new CarService();
carService.getCars().then((result) => setCars(result.data.data));
}, []);
return (
<div>
<Grid columns={3} >
<Grid.Row>
{cars.map((car) => {
let image = carImages.find(i=>i.car.id===car.id);
return
<Grid.Column style={{marginBottom:"1em"}}>
<CarComponent car={car} imagePath={image === undefined ? undefined :image.imagePath}></CarComponent>
</Grid.Column>
})}
</Grid.Row>
</Grid>
</div>
);
}
2021 EDIT:
Disclaimer, this question was written when I was brand new to both HTML and JS. If you are here looking for help for an issue, this may not be helpful as my code was pretty much a mess. Apologies for the inconvenience. The question will stay up as per site guidelines.
ORIGINAL POST
I am attempting to use create a table using angularJS which draws information from a js list of cars to present them in a table. Per the specifications this must be done using Angular, despite the easier ways existing.
My current issue, is that instead of outputting the information, each column displays the formatting code ({{ car.Model }} , for example).
My current code is as follows:
<body ng-app="">
<h2>CARS IN THE LOT:</h2>
<div class="container" ng-app="sortApp" ng-controller="mainController">
<table class="table table-bordered table-striped">
<thead>
<tr>
<td>
Manufacturer
</td>
<td>
Model
</td>
<td>
<a href="#" ng-click = "sortReverse = !sortReverse">
Year
<span ng-show = "!sortReverse" class="fa fa-caret-down"></span>
<span ng-show="sortReverse" class="fa fa-caret-up"></span>
</a>
</td>
<td>
Stock
</td>
<td>
Price
</a>
</td>
<td>
Option
</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="roll in cars | orderBy:sortType:sortReverse | filter:searchCar">
<td>{{ car.Manufacturer }}</td>
<td>{{ car.Model }}</td>
<td>{{ car.Year }}</td>
<td>{{ car.Stock }}</td>
<td>{{ car.Price }}</td>
<td>{{ car.Option }}</td>
</tr>
</tbody>
</table>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.8/angular.min.js"></script>
<script src="cars.js"></script>
</body>
</html>
angular.module('sortApp', [])
.controller('mainController', function($scope) {
$scope.sortType = 'price'; // set the default sort type
$scope.sortReverse = false; // set the default sort order
$scope.searchCar = ''; // set the default search/filter term
//BUTTON
var button = document.createElement("button");
button.innerHTML = "increment";
button.addEventListener ("click", incrementStock(0));
// create the list of cars
$scope.cars = [
{ Manufacturer: cars[0].Manufacturer, Model: cars[0].Model, Year: cars[0].Year , Stock: cars[0].Stock , Price: cars[0].Stock , Option: button},
{ Manufacturer: cars[1].Manufacturer, Model: cars[1].Model, Year: cars[1].Year , Stock: cars[1].Stock , Price: cars[1].Stock , Option: button},
{ Manufacturer: cars[2].Manufacturer, Model: cars[2].Model, Year: cars[2].Year , Stock: cars[2].Stock , Price: cars[2].Stock , Option: button},
{ Manufacturer: cars[3].Manufacturer, Model: cars[3].Model, Year: cars[3].Year , Stock: cars[3].Stock , Price: cars[3].Stock , Option: button},
{ Manufacturer: cars[4].Manufacturer, Model: cars[4].Model, Year: cars[4].Year , Stock: cars[4].Stock , Price: cars[4].Stock , Option: button},
{ Manufacturer: cars[5].Manufacturer, Model: cars[5].Model, Year: cars[5].Year , Stock: cars[5].Stock , Price: cars[5].Stock , Option: button},
{ Manufacturer: cars[6].Manufacturer, Model: cars[6].Model, Year: cars[6].Year , Stock: cars[6].Stock , Price: cars[6].Stock , Option: button},
{ Manufacturer: cars[7].Manufacturer, Model: cars[7].Model, Year: cars[7].Year , Stock: cars[7].Stock , Price: cars[7].Stock , Option: button},
{ Manufacturer: cars[8].Manufacturer, Model: cars[8].Model, Year: cars[8].Year , Stock: cars[8].Stock , Price: cars[8].Stock , Option: button},
{ Manufacturer: cars[9].Manufacturer, Model: cars[9].Model, Year: cars[9].Year , Stock: cars[9].Stock , Price: cars[9].Stock , Option: button}
];
});
function incrementStock(number){
alert("function called");
}
With cars.js being as follows:
var cars = [
{"manufacturer":"Toyota",
"model":"Rav4",
"year":2008,
"stock":3,
"price":8500},
{"manufacturer":"Toyota",
"model":"Camry",
"year":2009,
"stock":2,
"price":6500},
{"manufacturer":"Toyota",
"model":"Tacoma",
"year":2016,
"stock":1,
"price":22000},
{"manufacturer":"BMW",
"model":"i3",
"year":2012,
"stock":5,
"price":12000},
{"manufacturer":"Chevy",
"model":"Malibu",
"year":2015,
"stock":2,
"price":10000},
{"manufacturer":"Honda",
"model":"Accord",
"year":2013,
"stock":1,
"price":9000},
{"manufacturer":"Hyundai",
"model":"Elantra",
"year":2013,
"stock":2,
"price":7000},
{"manufacturer":"Chevy",
"model":"Cruze",
"year":2012,
"stock":2,
"price":5500},
{"manufacturer":"Dodge",
"model":"Charger",
"year":2013,
"stock":2,
"price":16000},
{"manufacturer":"Ford",
"model":"Mustang",
"year":2009,
"stock":1,
"price":8000},
]
You are iterating "roll in cars" but instead using "car" while printing it. Replace it as (ng-repeat = "roll in cars" to ng-repeat = "car in cars"). In original var cars collection you have used small names for property and while assigning it to $scope.cars you are writing initial letter of property in capital (e.g. "Manufacturer: cars[0].Manufacturer" it should be "Manufacturer: cars[0].manufacturer"). Additionally I have refactored your code to iterate cars in loop and push it to $scope.cars.
angular.module('sortApp', [])
.controller('mainController', function($scope) {
$scope.sortType = 'price'; // set the default sort type
$scope.sortReverse = false; // set the default sort order
$scope.searchCar = ''; // set the default
var button = document.createElement("button");
button.innerHTML = "increment";
button.addEventListener("click", incrementStock(0));
var cars = [{
"manufacturer": "Toyota",
"model": "Rav4",
"year": 2008,
"stock": 3,
"price": 8500
},
{
"manufacturer": "Toyota",
"model": "Camry",
"year": 2009,
"stock": 2,
"price": 6500
},
{
"manufacturer": "Toyota",
"model": "Tacoma",
"year": 2016,
"stock": 1,
"price": 22000
},
{
"manufacturer": "BMW",
"model": "i3",
"year": 2012,
"stock": 5,
"price": 12000
},
{
"manufacturer": "Chevy",
"model": "Malibu",
"year": 2015,
"stock": 2,
"price": 10000
},
{
"manufacturer": "Honda",
"model": "Accord",
"year": 2013,
"stock": 1,
"price": 9000
},
{
"manufacturer": "Hyundai",
"model": "Elantra",
"year": 2013,
"stock": 2,
"price": 7000
},
{
"manufacturer": "Chevy",
"model": "Cruze",
"year": 2012,
"stock": 2,
"price": 5500
},
{
"manufacturer": "Dodge",
"model": "Charger",
"year": 2013,
"stock": 2,
"price": 16000
},
{
"manufacturer": "Ford",
"model": "Mustang",
"year": 2009,
"stock": 1,
"price": 8000
},
];
// create the list of cars
$scope.cars = [];
for (var i = 0; i < cars.length; i++) {
var carObj = {
Manufacturer: cars[i].manufacturer,
Model: cars[i].model,
Year: cars[i].year,
Stock: cars[i].stock,
Price: cars[i].price,
Option: button
}
$scope.cars.push(carObj);
}
});
function incrementStock(number) {
alert("function called");
}
body{background-color: #95b8cf;}
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 3px solid #000000;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<body ng-app="sortApp">
<h2>CARS IN THE LOT:</h2>
<div class="container" ng-app="sortApp" ng-controller="mainController">
<table class="table table-bordered table-striped">
<thead>
<tr>
<td>
Manufacturer
</td>
<td>
Model
</td>
<td>
<a href="#" ng-click="sortReverse = !sortReverse">
Year
<span ng-show = "!sortReverse" class="fa fa-caret-down"></span>
<span ng-show="sortReverse" class="fa fa-caret-up"></span>
</a>
</td>
<td>
Stock
</td>
<td>
Price
</a>
</td>
<td>
Option
</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="car in cars | orderBy:sortType:sortReverse | filter:searchCar">
<td>{{ car.Manufacturer }}</td>
<td>{{ car.Model }}</td>
<td>{{ car.Year }}</td>
<td>{{ car.Stock }}</td>
<td>{{ car.Price }}</td>
<td>{{ car.Option }}</td>
</tr>
</tbody>
</table>
</div>
</body>
This question already has answers here:
Why does a js map on an array modify the original array?
(5 answers)
Updating an array of objects without mutation
(1 answer)
Why does map mutate array of objects?
(3 answers)
Closed 3 years ago.
I have a structure like this:
let MainItem = [
{
"Id": "1",
"Cost": "1000"
},
{
"Id": "2",
"Cost": "5000"
},
{
"Id": "3",
"Cost": "2000"
},
{
"Id": "4",
"Cost": "3000"
}
];
I am going to change the value of Cost each of the elements with map() loop and store it in NewMainItem.
let NewMainItem = MainItem.map((item, i) => {
item.cost = item.cost + 1000
return item
});
console.log(MainItem)
console.log(NewMainItem)
The main problem is that by changing Cost in NewMainItem, the value of
Cost in MainItem will be changed too, but I don not want to do this. By using map() loop why the main object (MainItem ) will be changed too?
You should use map to create new objects from the existing ones, not modify them.
You could use the spread operator to create a new item like this:
{ ...item, Cost: parseFloat(item.Cost) + 100 };
Here parseFloat was used because the Costs values are set as string in your snippet. Feel free to change this as it fits your needs.
See below snippet:
let MainItem = [
{
"Id": "1",
"Cost": "1000"
},
{
"Id": "2",
"Cost": "5000"
},
{
"Id": "3",
"Cost": "2000"
},
{
"Id": "4",
"Cost": "3000"
}
];
let NewMainItem = MainItem.map((item, i) => {
return { ...item, Cost: parseFloat(item.Cost) + 100 };
});
console.log(MainItem)
console.log(NewMainItem)
You could assign a new object with the wanted changed value.
var mainItem = [{ Id: "1", Cost: "1000" }, { Id: "2", Cost: "5000" }, { Id: "3", Cost: "2000" }, { Id: "4", Cost: "3000" }],
newMainItem = mainItem.map(item => Object.assign({}, item, { Cost: +item.Cost + 1000 }));
console.log(mainItem);
console.log(newMainItem);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have the following array:
var curr = [{
"Year": 2019,
"Title": "Asset Management Sample",
"Sum": 1020000.0,
"Budget":0
}, {
"Year": 2019,
"Title": "Monday test 2",
"Sum": 2546658.0,
"Budget":100
}, {
"Year": 2020,
"Title": "Asset Management Sample",
"Sum": 1020000.0,
"Budget":1000
}, {
"Year": 2020,
"Title": "Monday test 2",
"Sum": 3472000.0,
"Budget":100
}, {
"Year": 2021,
"Title": "Asset Management Sample",
"Sum": 1020000.0,
"Budget":100
}, {
"Year": 2021,
"Title": "Monday test 2",
"Sum": 2452000.0,
"Budget":100
}]
That I need to change to:
[{
"Year": 2019,
"Asset Management Sample": 1020000.0,
"Monday test": 2546658.0
}, {
"Year": 2020,
"Asset Management Sample": 1020000.0,
"Monday test 2": 3472000.0
}, {
"Year": 2021,
"Asset Management Sample": 1020000.0,
"Monday test 2": 2452000.0
}]
With help from earlier posters I have used .reduce (slightly modified from below) to generate this:
var res = arr.reduce(function(acc, curr) {
acc[curr.Year] = acc[curr.Year];
acc[curr.Year] = acc[curr.Year] || { Year: curr.Year } ;
acc[curr.Year][curr.Title] = curr.Sum;
return acc;
I need to expand this to include a sum of all the budget values for each year (there should be a single budget value per year). I added the following line in before the return:
acc[curr.Year][curr.Budget] = curr[curr.Budget] || { Budget: curr.Budget } ;
It is adding individual entries for each Budget value. How do I sum the Budget values and return it without affecting the other returned array?
Use reduce like so:
const arr = [{"Year":2019,"Title":"Asset Management Sample","Sum":1020000},{"Year":2019,"Title":"Monday test 2","Sum":2546658},{"Year":2020,"Title":"Asset Management Sample","Sum":1020000},{"Year":2020,"Title":"Monday test 2","Sum":3472000},{"Year":2021,"Title":"Asset Management Sample","Sum":1020000},{"Year":2021,"Title":"Monday test 2","Sum":2452000}];
const res = Object.values(arr.reduce((acc, { Year, Title, Sum }) => (acc[Year] = acc[Year] || { Year }, acc[Year][Title] = Sum, acc), {}));
console.log(res);
More verbose version:
const arr = [{"Year":2019,"Title":"Asset Management Sample","Sum":1020000},{"Year":2019,"Title":"Monday test 2","Sum":2546658},{"Year":2020,"Title":"Asset Management Sample","Sum":1020000},{"Year":2020,"Title":"Monday test 2","Sum":3472000},{"Year":2021,"Title":"Asset Management Sample","Sum":1020000},{"Year":2021,"Title":"Monday test 2","Sum":2452000}];
const res = Object.values(arr.reduce((acc, { Year, Title, Sum }) => {
acc[Year] = acc[Year] || { Year };
acc[Year][Title] = Sum;
return acc;
}, {}));
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: auto; }
ES5 syntax:
var arr = [{"Year":2019,"Title":"Asset Management Sample","Sum":1020000},{"Year":2019,"Title":"Monday test 2","Sum":2546658},{"Year":2020,"Title":"Asset Management Sample","Sum":1020000},{"Year":2020,"Title":"Monday test 2","Sum":3472000},{"Year":2021,"Title":"Asset Management Sample","Sum":1020000},{"Year":2021,"Title":"Monday test 2","Sum":2452000}];
var res = arr.reduce(function(acc, curr) {
acc[curr.Year] = acc[curr.Year] || { Year: curr.Year };
acc[curr.Year][curr.Title] = curr.Sum;
return acc;
}, {});
res = Object.keys(res).map(function(key) {
return res[key];
});
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: auto; }