Reputation: 89
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
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:
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