News

Travis CI set up notes -- how to set up the Travis Continuous Integration server for building React apps and deploying to Firebase
Quick, React! -- my first draft at a modern React tutorial. Ready for review. Post questions, bugs, requests, on Piazza.
Learn React Task -- a personal activity to help everyone learn (or practice) building React apps the modern way
React: Old vs New -- a demonstration of how functional React is simpler to code than old React.
First Demo Task -- due Tuesday
Team React Setup Task and Four-Panel Storyboard Task -- What each team needs to have done before the second class!
Day One Task -- What each team needs to be ready to present on the first class!

When

TTh 11am - 12:20am

Where

Tech A110

Who

Chris Riesbeck

Resources

React Old and New

Recommended practices for programming React continue to change. Major changes have happened within the past year, and no doubt more will come. For the most part, these changes have moved React from a class-based approach, with heavy reliance on mutable state, to a function-based approach with immutable objects. The goal is to make writing React applications simpler and less error-prone.

To illustrate the differences, this page rewrites the class-based code for the Thinking in React tutorial (as of April, 2019) using more modern JavaScript and React functional style.

Class vs Functional Components

Here are the class-based components defined in step 2 of the tutorial, except for one component that we'll cover in the next section.

class ProductCategoryRow extends React.Component {
  render() {
    const category = this.props.category;
    return (
      <tr>
        <th colSpan="2">
          {category}
        </th>
      </tr>
    );
  }
}

class ProductRow extends React.Component {
  render() {
    const product = this.props.product;
    const name = product.stocked ?
      product.name :
      <span style={{color: 'red'}}>
        {product.name}
      </span>;

    return (
      <tr>
        <td>{name}</td>
        <td>{product.price}</td>
      </tr>
    );
  }
}

class SearchBar extends React.Component {
  render() {
    return (
      <form>
        <input type="text" placeholder="Search..." />
        <p>
          <input type="checkbox" />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
}

class FilterableProductTable extends React.Component {
  render() {
    return (
      <div>
        <SearchBar />
        <ProductTable products={this.props.products} />
      </div>
    );
  }
}

These are all exactly the kinds of components that don't need to be classes. All they do is render some data into HTML. That's not a lucky coincidence. The Thinking in React process starts by first building a static display of data, with no state. Hence classes are not needed.

The above components are fairly eay to redefine as stateless functional components. Instead of a class with a render() method that returns HTML, we define a function that returns HTML directly. The props are passed as a parameter to the function.

Even more simplification can come by taking advantage of modern JavaScript's arrow function syntax and destructuring assignment:

const ProductCategoryRow = ({category}) => (
  <tr>
    <th colSpan="2">
      {category}
    </th>
  </tr>
);

const ProductRow = ({product}) => {
  const name = product.stocked ?
    product.name :
    <span style={{color: 'red'}}>
      {product.name}
    </span>;

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
};

const SearchBar = () => (
  <form>
    <input type="text" placeholder="Search..." />
    <p>
      <input type="checkbox" />
      {' '}
      Only show products in stock
    </p>
  </form>
);

const FilterableProductTable = ({products}) => (
  <div>
    <SearchBar />
    <ProductTable products={products} />
  </div>
);

The main benefit is reducing the boilerplate. No extends React.Component, no return render, no this.props.

Mapping vs For Loops

There's one other component defined in step 2 of the tutorial:

class ProductTable extends React.Component {
  render() {
    const rows = [];
    let lastCategory = null;

    this.props.products.forEach((product) => {
      if (product.category !== lastCategory) {
        rows.push(
          <ProductCategoryRow
            category={product.category}
            key={product.category} />
        );
      }
      rows.push(
        <ProductRow
          product={product}
          key={product.name} />
      );
      lastCategory = product.category;
    });

    return (
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Price</th>
          </tr>
        </thead>
        <tbody>{rows}</tbody>
      </table>
    );
  }
}

This is very sequential imperative code. It uses conditional push() calls in a forEach() to add a new "product category" row whenever the product category changes.

There are better ways to organize the data in the first place, and this code only works if the data has been sorted to put all categories together. But let's stick with the same input, and improve this code using a more functional approach. First, we can collect a list of the categories. To get a list of categories with no duplicates, we can use a Set. Here's a function to do that:

const getCategories = products => Array.from(new Set(products.map(product => product.category)));

Then, we can map over the categories, and, for each category, collect the products in that category, using filter.

const getCategoryProducts = (category, products) => products.filter(product => product.category === category);

Let's define simple functions to render categories and products to HTML.

const categoryRow = (category) => <ProductCategoryRow category={category} key={category} />;

const productRow = (product) => <ProductRow product={product} key={product.name} />;

Now we can define a function that takes a category and list of products and returns a list of the HTML rows needed for that category. We'll take advantage of JavaScript's new spread operator.

const categoryRows = (category, products) => [
  categoryRow(category),
  ...getCategoryProducts(category, products).map(productRow)
];

Finally, we can define ProductTable to collect all the rows for all the categories. We'll use JavaScript's new flatMap function to collect a list of [category, ...products] into a flat list of rows.

const ProductTable = ({products}) => {
  const categories = getCategories(products);
  const rows = categories.flatMap(category => categoryRows(category, products));

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

CodePen for the above code is below. Note that, as in the tutorial, filtering is not implemented yet.

See the Pen Thinking In React: Step 2 by Chris Riesbeck (@criesbeck) on CodePen.

Class State vs useState()

The next section of the Thinking in React tutorial introduces two pieces of local state:

These variables are changed by the form in the search bar component, and used by the code that displays the list of products. Here's the class-based code that creates that state and passes it to those components:

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

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

With the release of React 16.8, the useState() hook function is available for creating trackable state in a functional component. To use it, normally you would import it at the top of your code file, e.g.,

import React, { useState } from 'react';

We will write React.useState() here, so that the CodePen can run without imports.

useState(initial-value) returns an array of two elements: initial-value and a function to use to change the internal state to a new value. It's common to use array destructuring to assign these two values to some well-named variables, e.g.,

const [counter, setCounter] = React.useState(0);

So, to get our two pieces of state for filtering, we could do this:

  const [inStockOnly, setInStockOnly] = useState(false);
  const [filterText, setFilterText] = React.useState('');

We could then pass inStockOnly and filterText to ProductTable, and all four variables to SearchBar.

But whenever you find yourself passing many properties to components, consider packaging related properties together into a new object. In this case, both items of state are used together to decide what products to show. A product should be shown only if it includes the filter text and either inStockOnly is false, or the product is in stock. Therefore, let's define a function to create a product filter. The filter will have the state variables, methods for getting and setting those variables, and a method to filter the list of products.

An important React convention is to give a functions that create state a name that starts with use. This is so linting programs can check that your code obeys the rules of hooks .
const useFilter = () => {
  const [inStockOnly, setInStockOnly] = React.useState(false);
  const [text, setText] = React.useState('');
  const test = product => (
    (!inStockOnly || product.stocked) && product.name.indexOf(text) !== -1
  );

  return {
    filter(products) { return products.filter(test); },
    text,
    inStockOnly,
    setInStockOnly,
    setText
  };
}

The filtering part is easy with JavaScript's filter() function.

Now we only need to make some simple additions to our functional FilterableProductTable.

const FilterableProductTable = ({products}) => {
  const filter = useFilter();
  return (
    <div>
      <SearchBar
        filter={filter}
      />
      <ProductTable
        products={filter.filter(products)}
      />
    </div>
  );
}

The original version of SearchBar with filter state looked like this:

class SearchBar extends React.Component {
  render() {
    const filterText = this.props.filterText;
    const inStockOnly = this.props.inStockOnly;

    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={filterText} />
        <p>
          <input
            type="checkbox"
            checked={inStockOnly} />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
}

With our functional version and a filter object, it looks like this

const SearchBar = ({filter}) => (
  <form>
    <input
      type="text"
      placeholder="Search..."
      value={filter.text} />
    <p>
      <input
        type="checkbox"
        checked={filter.inStockOnly} />
      {' '}
      Only show products in stock
    </p>
  </form>
);

When the tutorial added filtering to the original ProductTable, tests were added to the forEach() loop, along with the tests for adding a category row.

class ProductTable extends React.Component {
  render() {
    const filterText = this.props.filterText;
    const inStockOnly = this.props.inStockOnly;

    const rows = [];
    let lastCategory = null;

    this.props.products.forEach((product) => {
      if (product.name.indexOf(filterText) === -1) {
        return;
      }
      if (inStockOnly && !product.stocked) {
        return;
      }
      if (product.category !== lastCategory) {
        rows.push(
          <ProductCategoryRow
            category={product.category}
            key={product.category} />
        );
      }
      rows.push(
        <ProductRow
          product={product}
          key={product.name}
        />
      );
      lastCategory = product.category;
    });

    return (
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Price</th>
          </tr>
        </thead>
        <tbody>{rows}</tbody>
      
    );
  }
}

All that logic is now encapsulated in the product filter hook, and called very simply in FilterableProductTable. We don't need to add anything to our ProductTable component at all!

CodePen for the above code (not yet interactive):

See the Pen Thinking In React: Step 2 by Chris Riesbeck (@criesbeck) on CodePen.

Changing State

The last section of the Thinking in React tutorial added the event handlers to the search bar form to change the state variables. That section changed the search bar component to this:

class SearchBar extends React.Component {
  constructor(props) {
    super(props);
    this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
    this.handleInStockChange = this.handleInStockChange.bind(this);
  }
  
  handleFilterTextChange(e) {
    this.props.onFilterTextChange(e.target.value);
  }
  
  handleInStockChange(e) {
    this.props.onInStockChange(e.target.checked);
  }
  
  render() {
    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={this.props.filterText}
          onChange={this.handleFilterTextChange}
        />
        <p>
          <input
            type="checkbox"
            checked={this.props.inStockOnly}
            onChange={this.handleInStockChange}
          />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
}

There's a lot of boilerplate here, because the code is using a class component. This code becomes much simpler with functional components. We just get the new values from the change event and store them. No playing with this at all.

const SearchBar = ({filter}) => {
  const handleFilterTextChange = e => filter.setText(e.target.value);
  const handleInStockChange = e => filter.setInStockOnly(e.target.checked);
  
  return (
    <form>
      <input
        type="text"
        placeholder="Search..."
        value={filter.text}
        onChange={handleFilterTextChange}
      />
      <p>
        <input
          type="checkbox"
          checked={filter.inStockOnly}
          onChange={handleInStockChange}
        />
        {' '}
        Only show products in stock
      </p>
    </form>
  );
}

CodePen for the above code (finally interactive!):

See the Pen Thinking In React: Step 2 by Chris Riesbeck (@criesbeck) on CodePen.