Mis à jour le mardi 7 mars 2017
  • 20 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Identify state

Connectez-vous ou inscrivez-vous pour bénéficier de toutes les fonctionnalités de ce cours !

So far, our app has no state. That is, there's essentially no user interaction, and nothing ever changes!

State information

Our task now is to identify what information can change as the user interacts with the app, and to represent that information in code as 'state'.  When we're done, the app still won't be interactive, but we'll have defined the bits of data that can change.

The most obvious element of state that we have beyond the form elements is the data model itself. We have buttons here that can delete list items, and a whole form that can create new ones. That means the list will have to be represented in state somewhere.

Since we already located that data in the  <Products>  component, we have a good idea that that's where it should live as state.

When we were building our static version of the app, we noted the highlighting for the current sort button. Keeping track of the current sort state is going to be another state concern. We'll locate that in the  <ProductTable>  component as  this.state.sort.column  and  this.state.sort.direction .

Form elements

The remaining elements of state are the values in the form elements. As mentioned, these are handled in a special way in React. We have four components with form elements that all affect the presentation of the products table. The filters and sort buttons will affect what is displayed, and in what order. The product form and the delete buttons will affect the list directly.

Since the form and the filters live outside the product table component as we have defined it, the current list data and the currently applied filters will have to be stored in a shared component -- in this case, the  Products  component.

We've already located the list data there, so we just have to think about how to lift the state of the currently selected filters to Products component!

The DOM normally tracks input state for us in the input elements themselves. We want to move that state up to the Products component. To do this, we'll simply force the input to reflect the state from the parent component by passing in its current value via  props . This means the  <Products>  component will be responsible for passing the state of the filters down to the  <Filters>  and  <ProductTable>  components, and will also pass the state of the current list of products to  <ProductTable> :

 Products.js :

import React from 'react';
import Filters from './Filters.js';
import ProductTable from './ProductTable.js';
import ProductForm from './ProductForm';

var PRODUCTS = {
  '1': {id: 1, category: 'Musical Instruments', price: '$459.99', stocked: true, name: 'Clarinet'},
  '2': {id: 2, category: 'Musical Instruments', price: '$5,000', stocked: true, name: 'Harpsicord'},
  '3': {id: 3, category: 'Musical Instruments', price: '$11,000', stocked: false, name: 'Fortepiano'},
  '4': {id: 4, category: 'Furniture', price: '$799', stocked: true, name: 'Chaise Lounge'},
  '5': {id: 5, category: 'Furniture', price: '$1,300', stocked: false, name: 'Dining Table'},
  '6': {id: 6, category: 'Furniture', price: '$100', stocked: true, name: 'Bean Bag'}
};

class Products extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterText: '',
      inStockOnly: false,
      products: PRODUCTS
    };

  }
  render() {
    return (
      <div>
        <Filters
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        ></Filters>
        <ProductTable
          products={this.state.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        ></ProductTable>
        <ProductForm ></ProductForm>
      </div>
    );
  }
}

export default Products;

This is how the inputs will be bound to the appropriate  props  values in  <Filters> :

 Filters.js :

import React from 'react';

class Filters extends React.Component {
  render() {
    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={this.props.filterText}
        />
        <p>
          <input
            type="checkbox"
            checked={this.props.inStockOnly}
          />
          &nbsp;
          Only show products in stock
        </p>
      </form>
    );
  }
}

export default Filters;

 <ProductTable>  now has to take list of products, filter it according to the currently selected filters and then sort the resulting list according to the currently selected sorting values. This means it has to know:

  •   The state of the current list:

    • this is already passed in via  props.products 

  •   The state of the currently selected filters:

    • this is already passed in via  props.filterText  and  props.inStockOnly 

  •   The state of the currently selected sort options:

    • the sorting is done from the table headers, which will be a child component of the table itself. Therefore, the current sorting state should live in the table, and be passed down to the sorting buttons just as we did from  <Products>  to  <Filters> .

This is also a good moment to implement our sorting and filtering operations!

We'll use a sorting function  SortByKeyAndOrder  to order the list correctly, and do our filtering while mapping the resultant array to React elements.

 ProductTable.js :

import React from 'react';
import ProductRow from './ProductRow.js';
import SortableColumnHeader from './SortableColumnHeader.js';

class ProductTable extends React.Component {
  constructor(props) {
    super(props);
    this.sortByKeyAndOrder = this.sortByKeyAndOrder.bind(this);
    this.state = {
      sort: {
        column: 'name',
        direction: 'desc'
      }
    };
  }
  sortByKeyAndOrder(objectA, objectB) {
    let isDesc = this.state.sort.direction === 'desc' ? 1 : -1;
    let [a, b] = [objectA[this.state.sort.column], objectB[this.state.sort.column]];
    if (this.state.sort.column === 'price') {
      [a, b] = [a, b].map((value) => parseFloat(value.replace(/[^\d\.]/g, ''), 10));
    }
    if (a > b) {
      return 1 * isDesc;
    }
    if (a < b) {
      return -1 * isDesc;
    }
    return 0;
  }
  sortProducts() {
    let productsAsArray = Object.keys(this.props.products).map((pid) => this.props.products[pid]);
    return productsAsArray.sort(this.sortByKeyAndOrder);
  }
  render() {
    var rows = [];
    this.sortProducts().forEach((product) => {
      if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
        return;
      }
      rows.push(<ProductRow product={product} key={product.id}></ProductRow>);
    });

    return (
      <div>
        <table>
          <thead>
            <tr>
              <SortableColumnHeader
                currentSort={this.state.sort}
                column="name"
              ></SortableColumnHeader>
              <SortableColumnHeader
                currentSort={this.state.sort}
                column="price"
              ></SortableColumnHeader>
            </tr>
          </thead>
          <tbody>{rows}</tbody>
        </table>
      </div>
    );
  }
}

export default ProductTable;

Now that we have the actual current sorting values, we'll reference them inside the headers to mark the currently selected sort button with the CSS  className  we've already defined:

 SortableColumnHeader.js :

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

class SortableColumnHeader extends React.Component {
  render() {
    let currentSort = this.props.currentSort.column === this.props.column ? this.props.currentSort.direction : false;
    return(
      <th>
        {this.props.column}
        <button
          className={currentSort === 'asc' ? 'SortableColumnHeader-current' : ''}
        >&#x25B2;</button>
        <button
          className={currentSort === 'desc' ? 'SortableColumnHeader-current' : ''}
        >&#x25BC;</button>
      </th>
    );
  }
}

export default SortableColumnHeader;

Let's look at the rendered app now.

Nothing should look different, but the form elements should work differently now. That is, they shouldn't work at all!

Typing in the search field should render nothing, and the checkbox and other buttons shouldn't change as you click on them. That's because we're controlling the value of all those input elements. In the next section we'll handle taking the user input.

Exemple de certificat de réussite
Exemple de certificat de réussite