info perto
info perto

Reputation: 89

How to calculate sum of items and total price with React?

I have a react app where I fetch Products details from a Json file. They are displayed properly and Increment-Decrement buttons work well.

So in index.js three js components are called main.js , header.js ,footer.js.

Main gets the json file create the container and row then calls 8 times(because there 8 items in Json) product.js and In Product.js all info about product and individual buttons are displayed on page.

Here is my question: What is the easiest way to get each items quantity multiply with relate price and add Total quantity and Total price in header?

index

import React from "react";
import ReactDOM from "react-dom";
import Main from "./components/main";
import Footer from "./components/footer";
import Header from "./components/header";
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';


ReactDOM.render(<Main />, document.getElementById("root"));
ReactDOM.render(<Header />, document.getElementById("header"));
ReactDOM.render(<Footer />, document.getElementById("footer"));

Header

import React, { Component } from "react";




class header extends Component {
    state = {
        totalPrice: 200,
        totalQuantity:0
    };
    render() {
        return (
            <div>
                <nav className="navbar navbar-expand-lg navbar-dark bg-info">
                    <a className="navbar-brand" href="#">
                        <img src="./logo.png" id="logo" alt="" />
                    </a>
                    <button
                        className="navbar-toggler"
                        type="button"
                        data-toggle="collapse"
                        data-target="#navbarNavDropdown"
                        aria-controls="navbarNavDropdown"
                        aria-expanded="false"
                        aria-label="Toggle navigation"
                    >
                        <span className="navbar-toggler-icon" />
                    </button>
                    <div className="collapse navbar-collapse" id="navbarNavDropdown">
                        <ul className="navbar-nav">
                            <li className="nav-item active">
                                <a className="nav-link" href="#">
                                    Home <span className="sr-only">(current)</span>
                                </a>
                            </li>
                            <li className="nav-item">
                                <a className="nav-link" href="#">
                                    Features
                </a>
                            </li>
                            <li className="nav-item">
                                <a className="nav-link" href="#">
                                    Pricing
                </a>
                            </li>

                        </ul>


                    </div>
                    <input className="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search"></input>
                    <button className="btn btn-success m-2" type="submit">Search</button>
                    <h2><span className={this.getClass()}>
                        Total Quantity:
                        Total Price: {this.formatCount()} 
                    </span></h2>

                </nav>
            </div>
        );
    }
    getClass() {
        let classes = "badge";
        classes += this.state.totalPrice === 0 ? " badge-danger" : " badge-warning";
        return classes;
    }

    formatCount() {
        const { totalPrice } = this.state;
        return totalPrice === 0 ? "Your Cart is Empty" : totalPrice+"€";
    }
}

export default header;

Main

import React, { Component } from 'react';
import ProductInfo from '../plist.json';
import Product from './product'



class Products extends Component {


  render() {
    return (

        <div className="container">
          <div className="row ">
          {ProductInfo.map(postDetail => <Product {...postDetail} />)}
          </div>
        </div>

    )
  }
}

export default Products

Product

import React, { Component } from 'react';


class Product extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };

    }


    handleIncerement = () => {
        this.setState({
            count: this.state.count + 1
        });

    }

    handleDecrement = () => {
        if(this.state.count< 1){
            this.setState({
              count:0
            });
          }else {
            this.setState({
              count: this.state.count- 1
            });
          }
    }

    render() {
        const { name, image, price, description } = this.props;
        let totalQuantity= 0;
        let totalPrice = 0;
        totalQuantity += this.state.count;
        totalPrice += this.state.count * {price};
        console.log("Quantity:"+ totalQuantity);
        console.log("Total Price:"+ totalPrice);



        return (
            <div className="col-md-4 ml-auto">
                <img className="productpic" src={require(`./images/${image}`)} alt="Product" />
                <h2 className="display-6"> <a href="{url}">{name}</a></h2>
                <p className="h5 price">{price}</p>
                <p className="info">{description}</p>
                <div className="counter">
                    <button className="btn btn-info" onClick={this.handleIncerement}>+</button>
                    <div className="count">{this.state.count}</div>
                    <button className="btn btn-info" onClick={this.handleDecrement}>-</button>
                </div>
            </div>
        );
    }
}

export default Product

Upvotes: 1

Views: 17775

Answers (1)

0xc14m1z
0xc14m1z

Reputation: 3725

I think that here you are missing a concept of React. You should keep your state high in your hierarchy of components if you need it below.

In this example, you have something in a component Main that you need in a sibling component Header. This means that you should have a parent component that pass this information down to them.

For instance, you can have an App component which somehow takes the JSON and keep it into his state alongside other product information:

// App.js
import React, { Component } from 'react'
import PRODUCTS from '../plist.json'

class App extends Component {

  state = {
    // here we are preparing the state copying all the
    // information of a product plus a quantity property set to 0
    products: PRODUCTS.map(p => ({ ...p, quantity: 0 }))
  }

  render() {
    return (
      <>
        {/* here we should render the two components who needs data */}
        <Footer />
      </>
    )
  }

}

In the render method we can render the three initial components but with some changes...

First, the Header requires the total quantity and the total price. One of React best practices tells us that everything that can be computed from the state, should be outside of it. In this case, we don't need to save these two quantities in the state, because we can easily compute them:

// in App class definition

...

totalQuantity = () =>
  this.state.products.reduce(
    (sum, product) => sum + product.quantity,
    0
  )

totalPrice = () =>
  this.state.products.reduce(
    (sum, product) => sum + product.quantity * product.price,
    0
  )

...

Being able to compute those values, we add the rendering of the Header component to the render method of App:

// in App class definition

...

render() {
  return (
    <>
      <Header quantity={ this.totalQuantity() } 
              price={ this.totalPrice() }
      />
      {/* here we should render the Main component */}
      <Footer />
    </>
  )
}

...

Of course you'll have to change the way you render these values in the Header component:

// Header component, render() method
// remember to apply some formatting for currency etc.
<span className={ this.getClass() }>
  Total Quantity: { this.props.quantity }
  Total Price: { this.props.price } 
</span>

Now, let's rethink a bit the Main component. It does two things:

  • render the products list;
  • handle increment/decrement of quantities;

Let's add Main to the render method and then work on these features:

// in App class definition

...

render() {
  return (
    <>
      <Header quantity={ this.totalQuantity() } 
              price={ this.totalPrice() }
      />
      <Main products={ this.state.products }
            onIncrement={ this.handleIncrement }
            onDecrement={ this.handleDecrement }
      />
      {/* here we should render the Footer component */}
    </>
  )
}

...

In the Main component we need to change the way we render the products because we don't read the JSON anymore, but we can use the data provided by App. In addition, we need to be able to pass increment and decrement events:

// Main

...

render() { return ( { this.props.products.map( (product, index) => this.props.onIncrement(index) } onDecrement={ () => this.props.onDecrement(index) } /> ) } ) }

...

Down in the Product component, we now don't need internal state anymore, because everything we need is provided as props, so it can be a stateless component:

const Product = ({
  image, 
  url, 
  name, 
  price, 
  description, 
  onIncrement, 
  quantity,
  onDecrement
}) => (
  <div className="col-md-4 ml-auto">
    <img className="productpic" 
         src={ require(`./images/${image}`) }
         alt="Product"
    />
    <h2 className="display-6">
      <a href="{url}">
        { name }
      </a>
    </h2>
    <p className="h5 price">
      { price }
    </p>
    <p className="info">
      { description }
    </p>
    <div className="counter">
      <button className="btn btn-info"
              onClick={ onIncrement }>
        +
      </button>
      <div className="count">
        { quantity }
      </div>
      <button className="btn btn-info"
              onClick={ onDecrement }>
        -
      </button>
    </div>
  </div>
)

To complete this behaviour, we need to handle the increment and decrement in the App component, in order to update the state and propagate updated information to Header (quantity and total) and Main.

// in App

...

handleIncrement = index =>
  this.setState(prevState => ({
    products: [
       ...prevState.products,
       [index]: {
          ...prevState.products[index],
          quantity: prevState.products[index].quantity + 1
       }
    ]
  }))

handleDecrement = index =>
  this.setState(prevState => ({
    products: [
       ...prevState.products,
       [index]: {
          ...prevState.products[index],
          quantity: prevState.products[index].quantity - 1
       }
    ]
  }))

...

We're almost done, in your index.js, render just the App component:

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/app";
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';


ReactDOM.render(<App />, document.getElementById("root"));

Upvotes: 1

Related Questions