Add total price to react shopping cart - reactjs

I'm new to react and followed this tutorial to built a shopping cart. The code is pretty simple and straight forward but I would like for it to display the total price of products. How would I implement that within this code?
Here's the code. I've later separated these into 3 files, "Products", "Cart" and "Productspage" but displayed it here all together so it would simpler to see it.
import React, {useState} from 'react'
import './ProductsPage.css'
const PAGE_PRODUCTS = 'products';
const PAGE_CART = 'cart';
function ProductsPage() {
    const [cart, setCart] = useState ([]);
    const [page, setPage] = useState (PAGE_PRODUCTS);
    const [products] = useState ([
        {
            name: 'Breakfast box ',
            cost:'9.99$',
            image: 'https://images.unsplash.com/photo-1578863950596-a74dfe8267b5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1573&q=80',
        },
        {
            name: 'Breakfast box ',
            cost:'8.99$',
            image: 'https://images.unsplash.com/photo-1557686652-6731ba12410f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80',
        },
    ])
    const addToCart = (product) => {
        setCart ([...cart, {...product}])
    }
    const removeFromCart = (productToRemove) => {
        setCart(cart.filter(product => product !== productToRemove))
    }
    const navigateTo = (nextPage) => {
        setPage(nextPage);
    };
    const renderProducts = () => (
        <>
         <h1>Products</h1>
            <div className="products">
            {products.map((product , index) => (
            <div className="product" key={index}>
               <h3>{product.name}</h3>
               <h4>{product.cost}</h4>
               <img src={product.image} alt={product.name}/>
               <button onClick={() => addToCart(product)}>
                   Add to Cart
                </button>
            </div>
                
                ))}
         </div>
         </>
    );
        const renderCart = () => (
            <>
            <h1>Cart</h1>
               <div className="products">
               {cart.map((product , index) => (
               <div className="product" key={index}>
                  <h3>{product.name}</h3>
                  <h4>{product.cost}</h4>
                  <img src={product.image} alt={product.name}/>
                  <button onClick={() => removeFromCart(product)}>
                      Remove
                   </button>
               </div>
                   
                   ))}
            </div>
            </>
        )
    return (
            <div className="productspage">
            <header>
                <button onClick={()=> navigateTo(PAGE_CART)}>
                    Go to Cart ({cart.length})
                </button>
                <button onClick={()=> navigateTo(PAGE_PRODUCTS)}>
                    View Products
                </button>
            </header>
            {page === PAGE_PRODUCTS && renderProducts()}
            {page === PAGE_CART && renderCart()}
            </div>
    );
};
export default ProductsPage;

you could simply loop through array created from object, and then convert string cost to numeric (by replacing $ to nothing and cast cart to numeric with + before that) and then sum up all of them like here:
Object.keys(cart).reduce(
(prevVal, currentVal) =>
prevVal + +cart[currentVal].cost.replace("$", ""),
0
)

Check it out i have added js code with comments to understand easily:
`const [total,setTotal]=useState(0)` //make a new state variable
const cart =[
{
name: 'Breakfast box ',
cost:'9.99$',
image: 'https://images.unsplash.com/photo-1578863950596-a74dfe8267b5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1573&q=80',
},
{
name: 'Breakfast box ',
cost:'8.99$',
image: 'https://images.unsplash.com/photo-1557686652-6731ba12410f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80',
},
]
//modified addToCart
const addToCart = (product) => {
const products = [...cart, product]
console.log(products)
const totalPrice = products.reduce((acc, curr)=>{ //calculate total
let cur =curr.cost.match(/\d./g).join('') //parse string to integer(cost)
return acc + Number(cur);
}, 0)
console.log("total:", totalPrice);
// setCart (products);
//setTotal(totalPrice);
}
//end addToCart
const newProduct = {
name: 'Breakfast box ',
cost:'7.99$',
image: 'https://images.unsplash.com/photo-1557686652-6731ba12410fixlib=rb1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=63',
}
addToCart(newProduct)

Call this function with your products list wherever you want the total cost to appear.
const getTotalCost = (productList) => {
return productList.reduce((totalCost, { cost: itemCost }) => totalCost + parseFloat(itemCost), 0);
};
This is just a regular JavaScript function that iterates the list of products and sums the cost key and returns it.
It's not accessing any props or state so can sit outside of your component body.

Related

Count the duplicates in a string array using React JS

Following is a code I implemented to create a bar chart using chart js in React app. Here it creates a bar chart with all the data in an array. But, I want to change this code only to give the output in the x-axis - destination, y-axis - no. of occurrence of this destination since it has many repeated destinations.
I searched methods to this but I couldn't get a correct solution.
Can anyone help me to do this?
const dataArrayY4 = [];
res.data.map(item => {
dataArrayY4.push(item.time)
})
const dataArrayX4 = []
res.data.map(item => {
dataArrayX4.push(item.destination)
})
this.setState({
data4: dataArrayY4,
labels4: dataArrayX4,
});
This could be done as follows:
const res = {
data: [
{ time: 1, destination: 'A'},
{ time: 3, destination: 'A'},
{ time: 2, destination: 'B'}
]
};
let tmp4 = [];
res.data.map((o, i) => {
const existing = tmp4.find(e => e.destination == o.destination);
if (existing) {
existing.time += o.time;
} else {
tmp4.push({time: o.time, destination: o.destination});
}
})
this.setState({
data4: tmp.map(o => o.time);
labels4: tmp.map(o => o.destination);
});
Above code could further be optimized by using Array.reduce() instead of Array.map().
I would make the code more efficient. Instead of dataArrayY4 being an array, I would make it an object that has a key of value and the number of occurrence of each value. This way, you can count all the number of occurrences of the all items in res.data
const dataArrayY4 = {};
res.data.map(item => {
dataArrayY4[item.destination] = (dataArrayY4[item.destination] || 0) + 1
})
const dataArrayX4 = []
res.data.forEach(item => {
dataArrayX4.push(item.destination)
})
this.setState({
data4: dataArrayY4,
labels4: dataArrayX4,
});
Then if you want to look for the occurrence of a particular value you
use this eg. Sri Lanka
this.state.data4['Sri Lanka']

Angular decrement value in array

I try to decrement a value in my array, but I can't get it to work.
My array data contains attributes and everytime a method gets clicked, I call that value from a service and increment it in the array object. The getter is equal to amountCounter.
My main problem is that whenever I try to remove an array object, my amountCounter won't also decrement the value which it had before, but the array object gets removed.
I also put two pictures to better clarify my problem, thank you so much for every help.
app.component.html
<h2>Add values of my service into array:</h2>
<p>Array:</p>
<p>Total: {{amountCounter}}</p>
<div *ngFor="let item of data, let i = index;">
<span>ID: {{item.id}}</span>
<span>Title: {{item.title}}</span>
<span (click)="removeElement(i, item.amountCounter)" class="material-icons">
close
</span>
</div>
app.component.ts
export class AppComponent {
clickEventsubscription: Subscription
ngOnInit() {
}
id: number;
title: String;
amountCounter: number;
data: any = [];
constructor(private share: ShareDataService) {
this.clickEventsubscription = this.share.getClickEvent().subscribe(() => {
this.initialize();
})
}
removeElement(id: number, counter: number) {
this.data.splice(id, 1);
this.amountCounter -= counter //In that line I can't get it to work that my attribute decrements
console.log("before" + this.amountCounter);
console.log("after:" + counter);
}
initialize() {
this.id = this.share.getId();
this.title = this.share.getTitle();
this.amountCounter = this.share.getAmountCounter();
const newData = {
id: this.id,
title: this.title,
amountCounter: this.amountCounter
};
this.data.push(newData);
console.log(this.data);
}
}
share-data.service.ts
export class ShareDataService {
private subject = new Subject<any>();
title: String;
id: number;
amountCounter: number;
getId() {
return this.id;
}
getTitle() {
return this.title;
}
getAmountCounter(){
return this.amountCounter;
}
sendClickEvent() {
this.subject.next();
}
getClickEvent(): Observable<any> {
return this.subject.asObservable();
}
}
That is how my array looks before ID 1 is clicked
That is how my array looks after I clicked at "X", but it decrements wrong
Thank you so much!
Not sure if this is the behavior you are after but generally this method will calculate the sum of the array values
getTotalAmount(): number {
return this.data.reduce((acc, item) => acc + item.amount, 0);
}
The main issue I found very difficult to figure out is that you have amountCounter in [share-data.service, dialog.component, app.component]
I suppose you want to add new items using dialog.component with different amount values.
Here you add new item to your 'data' array, the values for single item comes from share service which was updated in your dialog.component
initialize() {
console.log("initialize");
const id = this.share.getId();
const title = this.share.getTitle();
const amount = this.share.getAmount();
const newData = {
id,
title,
amount
};
this.data.push(newData);
}
To summarize the flow:
in dialog.component you update field values in share-data.service clickMe() method
that method will trigger a method in app.component called initialize which will add the new item to the this.data array.
if you click on item (to remove it) splice will do it, and Angular will refresh the Total calling the getTotalAmount method
Working Stackblitz.

How to write array with deleted image id?

When i remove images from news i catch id, and id come to along.
How to write in array all this lonlies id ?
How to create streamIds array with streamId ?
this.state = {
mainImage: null,
mainImageUrl: "",
crop: {
aspect: 2 / 1
},
pixelCrop: null,
cropped: false,
loaded: false,
streamIds: []
};
removeImage(imageKey, streamId) {
const {singleNews} = this.props;
let streamIds = this.state.streamIds;
console.log(streamId);
singleNews.secondaryImages.splice(imageKey, 1);
if (!singleNews.secondaryImages.length) {
singleNews.secondaryImages = null;
delete singleNews.secondaryImages;
this.props.updateSingleNews(null, singleNews);
} else {
streamIds.push(streamId);
singleNews.secondaryImages.map(image => {
const index = singleNews.secondaryImages.indexOf(image);
if (index > -1) {
singleNews.secondaryImages.slice(index, 1);
FilesApi.getDocument(image.streamId).then(resp => {
singleNews.secondaryImages[index] = new File([resp], image.name, {lastModified: Date.now()});
});
}
});
this.props.updateSingleNews('streamIds', streamIds);
}
}
this is your method
If not in this func where i need to place
if you want to keep the array of ids in the same component, use
let streamIds = [];
at the top of your react component and do
removeImage (imageKey, streamId) {
console.log(streamId);
streamIds.push(streamId); // insert the item to array
}
in your removeImage method
if you want to keep the removed ids in the application state, then the concept is the same, but it need to be done on the state management tool you are using (like redux, mobx etc)

reactJS and dynamic render and variable population

I want to dynamically generate output in my render() but I have run into a very strange situation. I have a process whereby I retrieve some data from a database using a fetch(). Once I get the data back, I determine the number of data records and then execute a for loop to populate an array with the returned data. I have a console.log() before the for loop to display the contents of my data receiving array and another console.log() as I populate the receiving array. For some reason, as I populate a specific occurrence of the array, all occurrences of the array appear to be changing. This is the entire code that I have:
import React from 'react';
import '../styles/app.css';
class testpage extends React.Component {
constructor(props) {
super(props);
this.state = {
productArray: []
};
}
componentWillMount() {
var tempData = '';
var numberOfRecords = 0;
let url = "http://wmjwwebapi-dev.us-west-2.elasticbeanstalk.com/api/getdata";
const options = { method: 'GET' };
fetch(url, options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
if (myJson == undefined)
{
console.log("fetch failed");
}
else
{
//inspect the data that the WebAPI returned
var return_code = myJson[0].return_code;
if (return_code == "Default Return code"){
tempData = '';
numberOfRecords = -2;
} else {
tempData = JSON.parse(myJson[0].return_string)
numberOfRecords = tempData.barcode.length;
var counter = 0;
var productArray = new Array;
var product = {
barcode: '',
name: '',
description: '',
image: '',
price: ''
}
for (counter=0;counter<numberOfRecords;counter++) {
product.barcode = tempData.barcode[counter];
product.name = tempData.name[counter];
productArray[counter] = product;
}
}
}
});
}
render() {
<div>
<div>
{this.state.productArray[0].barcode}
</div>
</div>
}
}
export default testpage;
Here is an image of what I see in the console.log() when the loop counter = 0:
Notice the barcode value of 5000159459228? This value gets pushed into productArray[0].barcode. And this is what I expected.
Here is an image of what I see in the console.log() when the loop counter = 1:
Here, the barcode of the record read is 5000159459230. This value should go into productArray1.barcode, which the image shows it does. However, the image also shows that the value of productArray[0].barcode has changed from 5000159459228 (the value of the first record) to 5000159459230 (the value of the second record).
Here is an image from the 3rd time through the loop:
Again, a new record with barcode = 5000159459231. It appears that this value gets pushed into productArray2.barcode but productArray[0].barcode and productArray1.barcode have now been changed.
How is that possible?
Eventually, the goal is to dynamically render the data that is retrieved.
I thank you in advance for any assistance.
Console output can generally be relied on for primitives but not for objects. Console implementation is specific to the environment where a script is executed (browser).
It is object reference that is being outputted, not object snapshot. When an object is changed, its representation in console can change as well.
Notice that in Chrome console the array was initially logged as [] empty array, while next log entry is [...] non-empty array.
To output object snapshot that doesn't change retroactively, use console.log(JSON.stringify(array)) or other serialized primitive value.
I would recommend to use chrome devtools for the debugging and add break point where array is populating.
Also you can add debugger in any where in code It will prompt devtools then.
I second #estus about changes reflect upon object updation where ever you have used Console.
After the chat and learned how the data shape is, with the help of Code Review, this is how we can do this:
const json = [{
return_string: {
"barcode": ["5000159459228", "5000159459229", "5000159459230"],
"scan_date": ["20180825173416", "20180825173416", "20180825173416"],
"name": ["Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)"],
"description": ["Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)", "Twix Twin Chocolate Bars - 50g - Pack of 6 (50g x 6 Bars) (1.76 oz x 6)"],
"image": ["http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg", "http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg", "http://thumbs2.ebaystatic.com/m/mv0sDuMCXy5TgjQFYC0CJAQ/140.jpg"],
"price": ["1", "2", "3"]
}
}];
const fakeRequest = () => new Promise( resolve =>
setTimeout( () => resolve( json ) )
);
class App extends React.Component {
state = {
data: "",
}
componentDidMount() {
fakeRequest()
.then( res => {
this.setState({
data: res[0].return_string,
})
})
}
renderData = () => {
const { data } = this.state;
const values = Object.values(data);
const keys = Object.keys(data);
const transposed = values[0].map((col, i) =>
values.map(row => row[i]));
const items = transposed.map(itemArr =>
keys.reduce((acc, key, i) => (
{ ...acc, [key]: itemArr[i] }), {})
);
return items.map( item => (
<div style={{ border: "1px solid black", marginBottom: "3px" }}>
<p>Barcode: {item.barcode}</p>
<p>Scan date: {item.scan_date}</p>
<p>Name: {item.name}</p>
<p>Description: {item.description}</p>
<p>Image: {item.image}</p>
<p>Price: {item.price}</p>
</div>
) )
}
render() {
return <div>{this.state.data && this.renderData()}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
The hard part is converting the data into an array of objects properly. Since, this shape of data is a little bit strange to me :) An object containing properties of arrays. And those array elements are matched by order. Phew :)

Best way to remove a specific item in AsyncStorage

I'm storing an array of objects in AsyncStorage and would like to know the best way to remove a specific object. Right now I´m passing an id to my function and then I loop through the array to match the id and remove the object and then the array in AsyncStorage is updated. This seems to work ok, but I wonder if this is optimal or if there is a better way to do this?
My function right now:
export function removeData(id) {
AsyncStorage.getItem('#books')
.then((books) => {
const updatedBooks = (JSON.parse(books))
for (let i = 0; i < updatedBooks.length; i++) {
if(updatedBooks[i].id == id) {
updatedBooks.splice(i, 1);
}
}
AsyncStorage.setItem('#books', JSON.stringify(updatedBooks));
})
}
My function for adding data to AsyncStorage:
export function addData(book) {
AsyncStorage.getItem('#books')
.then((books) => {
const b = books ? JSON.parse(books) : [];
b.push(book);
AsyncStorage.setItem('#books', JSON.stringify(b));
});
}
Button to add data with sample data to show structure:
<Button
title = "Add book"
onPress={() => addData({
id: 1,
title: 'Harry Potter',
author:'J.K. Rowling',
thumbnail:'https://covers.openlibrary.org/w/id/7984916-M.jpg',
})
To remove single item
AsyncStorage.removeItem('key', (err) => {
// key 'key' will be removed, if they existed
// callback to do some action after removal of item
});
To remove multiple items
let keys = ['k1', 'k2'];
AsyncStorage.multiRemove(keys, (err) => {
// keys k1 & k2 removed, if they existed
// callback to do some action after removal of item
});
Reference:
RemoveItem method
MultiRemove method

Resources