Fullstack React - Chapter 1 Notes

Summary

This post contains notes from the first chapter in Fullstack React - "Your first React Web Application". The purpose of this chapter is to get an introduction to React and how applications are composed using components, JSX, data flows, life cycle, and state.



Setting up your development environment

I'm not going to go into too many details about setting up my development environment (btw, I am developing on a Mac) because I already had most of the tools setup from developing Node.js apps. The list of tools that I am going to use include:

  • npm (Node Package Manager)
  • Git
  • Chrome web browser

Voting App

I started off by working with a sample project about a voting app. The project files for this app that came with the book included a lot of things that were setup already, like references to external packages (that are installed via "npm install") and package.json configuration, and server setup, and Babel, etc. I expect that the book is going to go into more details about setting these things up later and they are just including it now so we can get a sample up and running quickly.

  • This is a single page app, with index.html being the launching point for the rest of the application.
  • index.html will contain all of the links to the assets (javascript, stylesheets, etc.) that are needed to work in React. 
  • index.html also contains a link to Babel, which is used to transpile the ES6/ES7 code into ES5 code so it can be interpreted by any modern browser.
    • Line 20 in index.html tells Babel that it should handle this script, which means it needs to transpile that app.js script, which is the main javascript file that our code is saved to. 
  • index.html contains a div with the id="content". This is the main div where the rest of the application will be rendered into.
voting-app/public/index.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8">
    <title>Project One</title>
    <link rel="stylesheet" href="./semantic/dist/semantic.css" />
    <link rel="stylesheet" href="./style.css" />
    <script src="vendor/babel-standalone.js"></script>
    <script src="vendor/react.js"></script>
    <script src="vendor/react-dom.js"></script>
  </head>

  <body>
    <div class="main ui text container">
      <h1 class="ui dividing centered header">Popular Products</h1>
      <div id="content"></div>
    </div>
    <script src="./js/seed.js"></script>
    <script type="text/babel" data-plugins="transform-class-properties" src="./js/app.js"></script>
    
  </body>

</html>


Project Structure - Quick note on project structure...

One of the most confusing things for me when I tried piecing together various blog posts on React was understanding the project structure. For this app, the project structure is as follows:
  • voting-app
    • node_modules (this is where npm install will install external references)
    • public (this is where the code we write will live)
      • images
      • js
        • app.js (this is where we write all of our code, but this should be split into separate files in a real app)
      • semantic
      • vendor
      • some other files
      • index.html
      • style.css
    • tests
    • package.json

React Components

Components are at the core of React development. When building a web page, you want to try and break up the areas of a webpage into reusable pieces called components. For example, if you had a web page that shows a list of movies, you would have a component for the list of movies and a component for each individual movie in the list. The following is an example of a very basic React component.


voting_app/public/js/app.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class ProductList extends React.Component {
  render() {
    return (
      <div className='ui unstackable items'>
        Hello, friend! I am a basic React component.
      </div>
    );
  }
}

ReactDOM.render(
    <ProductList />,
    document.getElementById('content')
);


  • A component is an ES6 class that extends from the React.Component, which was included in line 10 above in index.html.
    • We could also declare a component using React.createClass.
  • A component must have a render() method
  • This component uses JSX (Javascript eXtension) syntax to define the contents of what is rendered. It is basically a familiar way to define the html that is rendered by React when a component is returned.
  • Lines 11 thru 14 tell ReactDom to render the ProductList component into the dom element with the id 'content' (the main div that I mentioned earlier).

Components can include other components

The following example shows how the ProductList component can include another component called Product:

voting_app/public/js/app.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class Product extends React.Component {
    constructor(props) {
        super(props);
        this.handleUpVote = this.handleUpVote.bind(this);
    }

    handleUpVote() {
        this.props.onVote(this.props.id);
    }

    render() {
        return (
            <div className='item'>
                <div className='image'>
                    <img src={this.props.productImageUrl}/>
                </div>
                <div className='middle aligned content'>
                    <div className='header'>
                        <a onClick={this.handleUpVote}>
                            <i className='large caret up icon' />
                        </a>
                        {this.props.votes}
                    </div>
                    <div className='description'>
                        <a href={this.props.url}>
                            {this.props.title}
                        </a>
                        <p>
                            {this.props.description}
                        </p>
                    </div>
                    <div className='extra'>
                        <span>Submitted by:</span>
                        <img className='ui avatar image' src={this.props.submitterAvatarUrl} />
                    </div>
                </div>
            </div>
        );
    }
}

class ProductList extends React.Component {
    handleProductUpVote(productId) {
        console.log(productId + ' was upvoted.');
    }

    render() {
        const products = Seed.products.sort((a, b) => (
            b.votes - a.votes
        ));
        const productComponents = products.map((product) => (
            <Product
                key={'product-' + product.id}
                id={product.id}
                title={product.title}
                description={product.description}
                url={product.url}
                votes={product.votes}
                submitterAvatarUrl={product.submitterAvatarUrl}
                productImageUrl={product.productImageUrl}
                onVote={this.handleProductUpVote}
            />
        ));
        return (
            <div className='ui unstackable items'>
                {productComponents}
            </div>
        );
    }
}

There is a lot going on in this example. The book builds up to this point, but I jumped a few steps, so let's go back and review a little:

  • First I created a new component called Product. This component holds the definition for an individual product in the product list.
    • constructor - this is needed so we can propagate a button click (handleProductUpVote) up to the parent component (ProductList) by binding the "this" context as it is in the constructor, which is the context of the entire component. This means that when we use the keyword "this" in the Product->handleUpVote method in the component, then it will refer to the Product component, so we are now able to access the component's properties from the handleUpVote method. Since we have access to the properties of the component, we can now access the onVote property, which holds a reference to the function in the parent ProductList component that we need to call.
    • render - this function uses properties that were passed in from ProductList. 
      • We access the properties via the {this.props.propertyname} syntax

  • The ProductList component was updated to include a list of Product components ({productComponents}).
    • The list of Products was built up by using the Product component and setting the properties for that component {product.propertyname
    • The list of properties that are passed down also includes an onVote property, which points to the handleUpVote method in the ProductList component.
    • You could include the Product component directly in the JSX for the ProductList, but since we had a list of them, we did it before we reached the JSX and just reference the variable that holds the list in the JSX.


  • Note about arrow functions (=>) - when an arrow function is used, it will bind the "this" keyword to the scope of the enclosing object. Regular anonymous functions will bind the "this" keyword to the global context. See page 39 for more explanation.

Using state

  • While props are passed into the component, this.state() is private to the component
  • When a state or prop is updated, a component will re-render itself.
  • To update the state use the this.setState() method, this will trigger the component to be re-rendered
  • An object's state should be immutable, so we should never do something like this.state.products = this.state.products.push(someProduct)
  • Sometimes you can get away with changing the state, but its best to just not fall into that habit even when it isn't necessary.
  • Adding Items to an array of objects in state
    • When dealing with arrays, you want to use the concat() method to create a new instance of an array and append elements to the new array, rather than push the new elements to the old array.

      1
      2
      3
      4
      5
      this.setState({ nums: [ 1, 2, 3 ]});
      
      const nextNums = this.state.nums.concat(4);
      
      this.setState({ nums: nextNums });

  • Updating Items in an array of objects in state
    • When updating an item in an array, there are 2 things to be careful to do to make sure we don't mess with the rule of making sure state is immutable.
    • First, is to use the map function to create a new instance of the array.
    • Second, is to use the Object.assign method to create a new object to copy the properties of the old object into that and then update the property of the new object.
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      handleProductUpVote(productId) {
          const nextProducts = this.state.products.map((product) => {
              if (product.id === productId) {
                  return Object.assign({}, product, {
                      votes: product.votes + 1,
                  });
              } else {
                  return product;
              }
          });
      
          this.setState({ products: nextProducts });
      }
      

React Lifecycle Methods

  • React provides a number of lifecycle methods. In this chapter, we are introduced to the componentDidMount method, which gives us a hook into the process, so we can perform some action when a component is mounted.
  • componentDidMount will fire after React renders the initial HTML to the DOM. So if we update state in the componentDidMount, this means the HTML is rendered to the DOM a second time because React components will re-render when the state is updated.
  • In this chapter, we use componentDidMount as a place to call this.setState to set the products object in the state to the list of products in Seed.products

Refactoring for transform-class-properties

  • In previous sections, we added a constructor to our components so we could set the context of the "this" keyword to access properties and state on the component from our other methods and event handlers. Babel provides a plugin called transform-class-properties that we can use to remove some of this boilerplate code, which will help simplify our classes.
  • We already had this property on our script tag for the app.js script, however, we weren't leveraging it in our previous code. In order to take advantage of this, we can refactor our classes by removing the constructor and changing the way we access state and properties.
  • The transform-class-properties plugin introduces the concept of property initializers.
  • By removing the constructor and changing handleUpVote to be an arrow function in the Product component, the "this" keyword inside of that function now refers to the context of the component and now we can access the properties of the component.
  • The same can be done in the ProductList component, which lets us change the way we initialize state and the handleProductUpVote method.

Final app.js with all components


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class ProductList extends React.Component {
    state = {
        products: [],
    };

    componentDidMount() {
        this.setState({ products: Seed.products });
    }

    handleProductUpVote = (productId) => {
        const nextProducts = this.state.products.map((product) => {
            if (product.id === productId) {
                return Object.assign({}, product, {
                    votes: product.votes + 1,
                });
            } else {
                return product;
            }
        });

        this.setState({ products: nextProducts });
    }

    render() {

        const products = this.state.products.sort((a, b) => (
            b.votes - a.votes
        ));
        const productComponents = products.map((product) => (
            <Product
                key={'product-' + product.id}
                id={product.id}
                title={product.title}
                description={product.description}
                url={product.url}
                votes={product.votes}
                submitterAvatarUrl={product.submitterAvatarUrl}
                productImageUrl={product.productImageUrl}
                onVote={this.handleProductUpVote}
            />
        ));
        return (
            <div className='ui unstackable items'>
                {productComponents}
            </div>
        );
    }
}

class Product extends React.Component {

    handleUpVote = () => (
        this.props.onVote(this.props.id)
    );

    render() {
        return (
            <div className='item'>
                <div className='image'>
                    <img src={this.props.productImageUrl}/>
                </div>
                <div className='middle aligned content'>
                    <div className='header'>
                        <a onClick={this.handleUpVote}>
                            <i className='large caret up icon' />
                        </a>
                        {this.props.votes}
                    </div>
                    <div className='description'>
                        <a href={this.props.url}>
                            {this.props.title}
                        </a>
                        <p>
                            {this.props.description}
                        </p>
                    </div>
                    <div className='extra'>
                        <span>Submitted by:</span>
                        <img className='ui avatar image' src={this.props.submitterAvatarUrl} />
                    </div>
                </div>
            </div>
        );
    }
}

ReactDOM.render(
    <ProductList />,
    document.getElementById('content')
);

Conclusion

That's it for chapter 1. In this chapter I learned about:
  • How to think about and organize React apps as components
  • Using JSX inside the render method
  • Data flows from parent to children through props
  • Event flows from children to parent through functions
  • Utilizing React lifecycle methods
  • Stateful components and how state is different from props
  • How to manipulate state while treating it as immutable
So far I am really enjoying the book Fullstack ReactI like having one resource with a consistent thought process for learning how to develop a React app. Their website describes the problems of learning by jumping from blog to blog as one of the main pain points that they are able to ease and so far I think they are absolutely right!

They give you chapter 1 for free and I took advantage of that. After going through chapter 1, I like what I see so far, so I am going to purchase the rest of book and hopefully stay dedicated enough to write more blog posts.

No comments:

Post a Comment

} else { }