Changing the State

The event management example that we looked at is very simple, but it only shows the basics of React event management. This example does not involve the state, and its management is straightforward. In many real-world cases, an event causes changes to the application's state, and that means changes to the component's state.

Suppose that, for example, you want to allow the selecting of products from the catalog. To do so, we add the selected property to each product object, as shown in the following array:

[
{"code":"P01",
"name": "Traditional Merlot",
"description": "A bottle of middle weight wine, lower in tannins
(smoother), with a more red-fruited flavor profile.",
"price": 4.5, "selected": false},
{"code":"P02",
"name": "Classic Chianti",
"description": "A medium-bodied wine characterized by a marvelous
freshness with a lingering, fruity finish",
"price": 5.3, "selected": false},
{"code":"P03",
"name": "Chardonnay",
"description": "A dry full-bodied white wine with spicy, bourbon-y
notes in an elegant bottle",
"price": 4.0, "selected": false},
{"code":"P04",
"name": "Brunello di Montalcino",
"description": "A bottle of red wine with exceptionally bold fruit
flavors, high tannin, and high acidity",
"price": 7.5, "selected": false}
]

When the user clicks on the product area, the value of the selected property is toggled and the background color of the area changes. The following code snippet shows the new version of the Product component:

import React from 'react';
import './Product.css'

class Product extends React.Component {
select() {
this.props.item.selected = !this.props.item.selected;
}

render() {
let classToApply = this.props.item.selected? "selected": "";

return <li onClick={() => this.select()} className={classToApply}>
<h3>{this.props.item.name}</h3>
<p>{this.props.item.description}</p>
</li>;
}
}

export default Product;

The select() method toggles the value of the selected property, while in the rendering method, we calculate the name of the class to apply according to the value of the selected property. The resulting class name is then assigned to the className attribute.

Unexpectedly, this code is not working correctly. You can verify it by performing the following steps. We can open the existing project, my-shop-02, in order to see the results of the previous code. Follow these steps:

  1. Open a console window
  2. Move to the my-shop-02 folder
  3. Run npm install
  4. Run npm start

The code is not working as expected because the select() method does not change the component's state, so the render() method is not triggered. In addition, keep in mind that the props property is read-only, so any change to it has no effect.

The Product component is a stateless component, so we have no state to change here. The product's data comes from the Catalog root component via props. So, how can we change the Catalog component's state, starting from an event triggered at the Product component instance?

Specifically, how can a child component change the state of its parent component?

Actually, the child component has no opportunity to change the parent component's state, because in a React component hierarchy, data flows in a unidirectional way, from the parent towards the children. We illustrate this flow in the following diagram:

We cannot push data from the child to the parent. In order for a child component to change the state of a parent component, we need to get a method to act on that state. Since a component state is accessible only by the component itself, the parent component must provide that method to its children via the props property.

Consider the following code:

import React from 'react';
import './Catalog.css';
import ProductList from './ProductList';

class Catalog extends React.Component {
constructor() {
super();
this.state = { products: [] };

fetch("products.json")
.then(response => response.json())
.then(json => {this.setState({products: json})})
.catch(error => console.log(error));
}

select(productCode) {
let productList = this.state.products.map(function(p) {
if (p.code === productCode) {
p.selected = (!p.selected);
}
return p;
});
this.setState({products: productList});
}

render() {
return <div><h2>Wine Catalog</h2><ProductList
items={this.state.products} selectHandler={this.select}/></div>;
}
}

export default Catalog;

The preceding code adds the select() method to the Catalog component. This method takes a product code as an input parameter, gets the product list from the component's state, and updates the selected property of the corresponding product. It then updates the component's state with the new product list.

The select() method is assigned to the new selectHandler attribute in the ProductList tag, so the corresponding component can access it through the props property.

The following code shows how this.props.selectHandler is passed from the ProductList component to the Product component via the selectHandler attribute:

import React from 'react';
import './ProductList.css';
import Product from './Product';

class ProductList extends React.Component {
render() {
let products = [];

for (let product of this.props.items) {
products.push(<Product item={product}
selectHandler={this.props.selectHandler}/>);
}

return <ul>{products}</ul>;
}
}

export default ProductList;

Finally, the Product component handles the onClick events by calling the select() method passed via the this.props.selectHandler property with the appropriate product code:

import React from 'react';
import './Product.css'

class Product extends React.Component {
render() {
let classToApply = this.props.item.selected? "selected": "";

return <li onClick={() => this.props.selectHandler
(this.props.item.code)} className={classToApply}>

<h3>{this.props.item.name}</h3>
<p>{this.props.item.description}</p>
</li>
}
}

export default Product;

We'll now open the existing project, my-shop-03, in order to see the results of the previous code. Follow these steps:

  1. Open a console window
  2. Move to the my-shop-03 folder
  3. Run npm install
  4. Run npm start

We can conclude that an event on a child component triggers the execution of a parent component method passed via props. The method changes the parent's state, and the effect of this change is once again propagated to the children via props. The following diagram illustrates this behavior:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.86.7