1

I have created an ecommerce site. Within my Product.js I have an onclick function for each product that pushes the product to local storage and updates the state of the shopping cart. However, my cart.js contains the totals like total products, taxes, total amount...etc. How can I add setState to get them to update when a product is added? I tried adding setState within the return section of the Cart.js but that ended up creating an endless loop of error messages. Below is the code:

import React,{Component} from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {faCartPlus} from "@fortawesome/free-solid-svg-icons";





class Products extends Component {
  constructor(props, context) {
   super(props)
   this.state={
    shoppingCart:[]
   }
  }

 
  addToCart=(item)=>{
  
    this.state.shoppingCart.push(item)
    this.setState({shoppingCart:this.state.shoppingCart})
    localStorage.setItem('cart',JSON.stringify(this.state.shoppingCart))

  }
  
  render() {
    
   
    return (
      <div className="container prod-cntr">
        <div className="row prod-row">
          {this.props.products?.map((element) => (
            <div className="col-lg-3 prod-col" key={element.id}>
              <div className="card card-container">
                <img
                  src={element.image}
                  alt="product img"
                  className="prod-img"
                />
                <div className="card-body">
                  <p className="card-title">{element.product}</p>
                  <p className="card-text">{element.description}</p>
                  <p className="prod-price">{element.price}  <FontAwesomeIcon icon={faCartPlus} className="prod-carticon" onClick={()=>{this.addToCart(element)}} /></p>
                  
                </div>
              </div>
            </div>
          ))}
        </div>
        <div>
              </div>
      </div>
    );
  }
}

export default Products;

import React, { Component } from "react";
import plus from "./assets/images/plus.svg";
import minus from "./assets/images/minus.svg";


class Cart extends Component{
    constructor(props){
        super(props)

this.state = {
    totalItems: 0,
    amount:0,
    taxes: 0,
    totalAmount: 0
}
        
    }
    render(){


const cartItems = JSON.parse( localStorage.getItem('cart'));
const totalItems = cartItems?.length || 0;
const amount = cartItems?.reduce((accumulator, object) => {
    return accumulator + object.price;},0) ||0;
const taxes = (amount * 0.065);
 const totalAmount = amount + taxes;

return(<>
<div>
<h2>YOUR CART</h2>
<p>Total Items <span>{this.state.totalItems} </span></p>
<p>Amount <span>{this.state.amount}</span></p>
<p>Total Taxes <span>{this.state.taxes}</span></p>
<p>Total Amount <span>{this.state.totalAmount}</span></p>

<p>Check Out</p>
</div>
      <div className="container prod-cntr">
        <div className="row prod-row">
          {cartItems?.map((element) => (
            <div className="col-lg-3 prod-col" key={element.id}>
              <div className="card card-container">
                <img
                  src={element.image}
                  alt="product img"
                  className="prod-img"
                />
                <div className="card-body">
                  <p className="card-title">{element.product}</p>
                  <p className="card-text">{element.description}</p>
                  <div className = "quantity-container">
                  <img src={minus} className ="minus"/> <p className ="quantity" >QUANTITIY:<span className="qnty-txt"></span></p> <img src={plus} className ="plus"/> 
                 </div>
                 <button onClick={localStorage.removeItem("item")}>Remove From Cart</button> 
                </div>
              </div>
            </div>
          ))}
        </div>
        <div>
        </div>
      </div>

        </>)
    }
}

export default Cart;
madQuestions
  • 109
  • 1
  • 7
  • 1
    You are [mutating state](https://reactjs.org/docs/state-and-lifecycle.html#do-not-modify-state-directly) with `this.state.shoppingCart.push(item)`. State in React is immutable and should be updated by creating a new copy of state and changing the parts you need – gloo Jul 06 '22 at 17:18
  • Hello. Usually beginner state tutorials involve the counter example. I see them use this.setState({count: this.state.count + 1}) Is that wrong or is that somehow the copy that you mentioned ? Are you saying I need to change mine to this.setState({shoppingCart: this.state.shoppingCart.push(item) – madQuestions Jul 06 '22 at 17:26
  • If you're setting a 'global' state (i.e. a cart that numerous components and children can use) I would *highly* suggest using ContextAPI for that. https://reactjs.org/docs/context.html (note: You can use Redux/etc for it as well, but for a simple cart you don't need all of the boilerplate that Redux needs) – Joel Hager Jul 06 '22 at 17:32
  • `this.setState({count: this.state.count + 1})` is not wrong as it is not mutating the state directly, it is setting `count` to be the sum of the current value of the `this.state.count` and 1. `this.setState({shoppingCart: this.state.shoppingCart.push(item)` is incorrect as `this.state.shoppingCart.push(item)` mutates the `shoppingCart` array in-place and then returns the new length of the array, so in the `setState` you would be setting `shoppingCart` to some integer – gloo Jul 06 '22 at 17:32
  • What I mean by "creating a new copy of state" is you want to copy your `shoppingCart` array, add your new `item` to that copy, then set your state to that new copy. This can be done in one line with [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax): `this.setState({shoppingCart: [...this.state.shoppingCart, item]})`. See this SO post for a more in-depth explanation: [Why can't I directly modify a component's state, really?](https://stackoverflow.com/questions/37755997/why-cant-i-directly-modify-a-components-state-really) – gloo Jul 06 '22 at 17:37

2 Answers2

0

you have to create createRef in class component, as you can see the below code I'm using createRef, You have to push the values in this.items and then you have to set the this.items in setState. I hope this would be helpful.

class Products extends Component {
    constructor(props, context) {
     super(props)
     this.state={
     }
     this.items = React.createRef([]);
    }
  
   
    addToCart=(item)=>{
      this.items.current.push(item);
      
      localStorage.setItem('cart',JSON.stringify(this.items.current))
  
    }
    
    render() {
      
     
      return (
        <div className="container prod-cntr">
          <div className="row prod-row">
            {this.props.products?.map((element) => (
              <div className="col-lg-3 prod-col" key={element.id}>
                <div className="card card-container">
                  <img
                    src={element.image}
                    alt="product img"
                    className="prod-img"
                  />
                  <div className="card-body">
                    <p className="card-title">{element.product}</p>
                    <p className="card-text">{element.description}</p>
                    <p className="prod-price">{element.price}  <FontAwesomeIcon icon={faCartPlus} className="prod-carticon" onClick={()=>{this.addToCart(element)}} /></p>
                    
                  </div>
                </div>
              </div>
            ))}
          </div>
          <div>
                </div>
        </div>
      );
    }
  }
  
  export default Products;
  

here second method:

let items = [];

class Products extends Component {
constructor(props, context) {
 super(props)
 this.state={
 }
}

componentDidMount() {
    items = [];
}

addToCart=(item)=>{
  items.push(item);
  console.log(items)


  localStorage.setItem('cart',JSON.stringify(items))

}

render() {
  
 
  return (
    <div className="container prod-cntr">
      <div className="row prod-row">
        {this.props.products?.map((element) => (
          <div className="col-lg-3 prod-col" key={element.id}>
            <div className="card card-container">
              <img
                src={element.image}
                alt="product img"
                className="prod-img"
              />
              <div className="card-body">
                <p className="card-title">{element.product}</p>
                <p className="card-text">{element.description}</p>
                <p className="prod-price">{element.price}  <FontAwesomeIcon icon={faCartPlus} className="prod-carticon" onClick={()=>{this.addToCart(element)}} /></p>
                
              </div>
            </div>
          </div>
        ))}
      </div>
      <div>
            </div>
    </div>
  );
}

}
  
  export default Products;

Cart.js

import React, {Component} from 'react';
import plus from './assets/images/plus.svg';
import minus from './assets/images/minus.svg';

class Cart extends Component {
  constructor(props) {
    super(props);

    this.state = {
      totalItems: 0,
      amount: 0,
      taxes: 0,
      totalAmount: 0
    };
  }

  removeItem = (id) => {
    const cartItems = JSON.parse(localStorage.getItem('cart'));
    const filter = cartItems.filter(item => item.id !== id);
    if (filter) {
        JSON.stringify(localStorage.setItem('cart', filter));
    }
  }

  render() {
    const cartItems = JSON.parse(localStorage.getItem('cart'));
    const totalItems = cartItems?.length || 0;
    const amount =
      cartItems?.reduce((accumulator, object) => {
        return accumulator + object.price;
      }, 0) || 0;
    const taxes = amount * 0.065;
    const totalAmount = amount + taxes;

    return (
      <>
        <div>
          <h2>YOUR CART</h2>
          <p>
            Total Items <span>{this.state.totalItems} </span>
          </p>
          <p>
            Amount <span>{this.state.amount}</span>
          </p>
          <p>
            Total Taxes <span>{this.state.taxes}</span>
          </p>
          <p>
            Total Amount <span>{this.state.totalAmount}</span>
          </p>

          <p>Check Out</p>
        </div>
        <div className="container prod-cntr">
          <div className="row prod-row">
            {cartItems?.map(element => (
              <div className="col-lg-3 prod-col" key={element.id}>
                <div className="card card-container">
                  <img src={element.image} alt="product img" className="prod-img" />
                  <div className="card-body">
                    <p className="card-title">{element.product}</p>
                    <p className="card-text">{element.description}</p>
                    <div className="quantity-container">
                      <img src={minus} className="minus" />{' '}
                      <p className="quantity">
                        QUANTITIY:<span className="qnty-txt"></span>
                      </p>{' '}
                      <img src={plus} className="plus" />
                    </div>
                    <button onClick={() => this.removeItem(element.id)}>Remove From Cart</button>
                  </div>
                </div>
              </div>
            ))}
          </div>
          <div></div>
        </div>
      </>
    );
  }
}

export default Cart;
Ahmad Faraz
  • 1,371
  • 1
  • 4
  • 11
  • This did not work. This stopped the items from being added to the local storage as well as populating in the cart component. Thanks for taking the time to add a suggestion though! – madQuestions Jul 06 '22 at 19:33
  • Ohh my bad, I have updated the answer. could you please try again. We have also pass the `this.items.current` in localStorage. – Ahmad Faraz Jul 06 '22 at 19:39
  • Still no luck. Now it says Uncaught Type Error: Cannon read properties of null (reading 'push') referring to line 20 which is "this.items.current.push(item)" – madQuestions Jul 06 '22 at 19:56
  • `this.items = React.createRef([]);` here we intialize the `[]`, So this should not be a mistake. – Ahmad Faraz Jul 06 '22 at 20:00
  • If still not luck, I have updated the answer and added second method. – Ahmad Faraz Jul 06 '22 at 20:05
  • I don't believe the issue is stating it's not declared. It has a problem with it being null – madQuestions Jul 06 '22 at 20:05
  • the second method is not updating local storage. – madQuestions Jul 06 '22 at 20:09
  • let me explain the second method, If we declare `items` variable in `addToCart` function so when we call the function it will update the value and remove the previous value in array that's why we decalred the variable outside the component. I added the `console` in `addToCart` function, can you please check in `Items`, whether values are being added or not. – Ahmad Faraz Jul 06 '22 at 20:14
  • let me change the variable name, maybe it's confusing you. – Ahmad Faraz Jul 06 '22 at 20:20
  • Nothing is being logged in the console. However, if I manually refresh the site, the images do appear in the cart but the local storage is not updated. – madQuestions Jul 06 '22 at 20:20
  • Did you click on `addToCart` function? because when you click on `addToCart` function your localStorage will be updated and values will be printed on console. – Ahmad Faraz Jul 06 '22 at 20:23
  • Yes. I'm referring to the add to cart button. When you press them nothing gets added to the console or local storage. However, if I then manually refresh the site, there is still no console message or local storage update but he object is rending in the cart. – madQuestions Jul 06 '22 at 20:29
  • sorry, the console message is updated. It's just that the local storage is not. – madQuestions Jul 06 '22 at 20:30
  • I think the issue may be that I have to refresh the page to render what was clicked. Do you know how to fix that? – madQuestions Jul 06 '22 at 20:33
  • Or it may be my removeItem onclick function is incorrect. Is this how you would code line 54 in Cart.js – madQuestions Jul 06 '22 at 20:35
  • let me check.... – Ahmad Faraz Jul 06 '22 at 20:37
  • you cannot `removeItem` from localStorage like this, I have update the answer you can check `removeItem` function, let me know If you don't understand I will explain. – Ahmad Faraz Jul 06 '22 at 20:46
  • LocalStorage should work, localStorage should not clear data when you refresh the page. can you please check you code, anywhere you are clearing the localStorage or clear the 'cart' – Ahmad Faraz Jul 06 '22 at 20:50
  • can you please check `localStorage.setItem('cart',JSON.stringify(items))` are you passing the `items`? – Ahmad Faraz Jul 06 '22 at 20:52
  • Hey Ahmad, yes. There is one object stuck in local storage from before we began updating the code. It remain in local storage, however, the new items I click do not go to local storage but they render within the cart component. (Also, your suggestion for deleting from local storage isn't working.) I'm trying to understand @Chiranjhivi Ghimire response to see if re-rendering can fix this. – madQuestions Jul 07 '22 at 00:57
-2

re-rendering the components after the button click will solve your problem. I have done one sample example for your type problem here:

https://codesandbox.io/s/stateupdatetest-pb811e

if you find any difficulties regarding the solution. Please reply on this thread.

Chiranjhivi Ghimire
  • 1,739
  • 1
  • 19
  • 21