Я думаю, что здесь вам не хватает концепции React. Вы должны поддерживать свое состояние на высоком уровне в иерархии компонентов, если вам это нужно ниже.
В этом примере у вас есть что-то в компоненте Main
, что вам нужно в компоненте-родственнике Header
. Это означает, что у вас должен быть родительский компонент, который передает им эту информацию.
Например, у вас может быть компонент App
, который каким-то образом принимает JSON и сохраняет его в своем состоянии вместе с другой информацией о продукте:
// 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 />
</>
)
}
}
В методе render
мы можем отобразить три начальных компонента, но с некоторыми изменениями ...
Во-первых, Header
требует общего количества и полной цены. Одна из лучших практик React говорит нам, что все, что можно вычислить из состояния, должно быть за его пределами. В этом случае нам не нужно сохранять эти две величины в состоянии, потому что мы можем легко вычислить их:
// 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
)
...
Имея возможность вычислять эти значения, мы добавляем рендеринг компонента Header
в метод рендеринга App
:
// in App class definition
...
render() {
return (
<>
<Header quantity={ this.totalQuantity() }
price={ this.totalPrice() }
/>
{/* here we should render the Main component */}
<Footer />
</>
)
}
...
Конечно, вам придется изменить способ рендеринга этих значений в компоненте Header
:
// 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>
Теперь давайте немного переосмыслим компонент Main
. Это делает две вещи:
- отображать список товаров;
- обрабатывать увеличение / уменьшение количества;
Давайте добавим Main
к методу рендеринга и затем поработаем над этими функциями:
// 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 */}
</>
)
}
...
В компоненте Main
нам нужно изменить способ рендеринга продуктов, потому что мы больше не читаем JSON, но мы можем использовать данные, предоставленные App
. Кроме того, нам нужно иметь возможность передавать события увеличения и уменьшения:
// Main
...
render () {
вернуть (
{
this.props.products.map (
(продукт, индекс) =>
this.props.onIncrement (index)}
onDecrement = {() => this.props.onDecrement (index)}
/>
)
}
)
}
...
Внизу в компоненте Product
нам больше не нужно внутреннее состояние, потому что все, что нам нужно, предоставляется в качестве реквизита, поэтому он может быть компонентом без состояния:
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>
)
Чтобы завершить это поведение, нам нужно обработать приращение и уменьшение в компоненте App
, чтобы обновить состояние и распространить обновленную информацию до Header
(количество и общее количество) и 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
}
]
}))
...
Мы почти закончили, в вашем index.js
, визуализируем только компонент App
:
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"));