Creating a container React component

Let's start by editing our application's main JavaScript file. Replace the contents of the ~/snapterest/source/app.js file with the following code snippet:

var React = require('react'),
var ReactDOM = require('react-dom'),
var Application = require('./components/Application.react'),

ReactDOM.render(<Application />, document.getElementById('react-application'));

There are only four lines of code in this file, and as you can guess, they provide document.getElementById('react-application') as a deployment target for the <Application /> component and render <Application /> to the DOM. The whole user interface for our web application will be encapsulated in one React component, Application.

Next, navigate to ~/snapterest/source/components/ and create the Application.react.js file inside this directory. All of our React components will have their filenames ending with react.js. This convention allows us to easily distinguish between React and non-React source JavaScript files.

Let's take a look at the contents of the Application.react.js file:

var React = require('react'),
var Stream = require('./Stream.react'),
var Collection = require('./Collection.react'),

var Application = React.createClass({

  getInitialState: function () {
    return {
      collectionTweets: {}
    };
  },

  addTweetToCollection: function (tweet) {
    var collectionTweets = this.state.collectionTweets;

    collectionTweets[tweet.id] = tweet;

    this.setState({
      collectionTweets: collectionTweets
    });
  },

  removeTweetFromCollection: function (tweet) {
    var collectionTweets = this.state.collectionTweets;

    delete collectionTweets[tweet.id];

    this.setState({
      collectionTweets: collectionTweets
    });
  },

  removeAllTweetsFromCollection: function () {
    this.setState({
      collectionTweets: {}
    });
  },

  render: function () {
    return (
      <div className="container-fluid">

        <div className="row">
          <div className="col-md-4 text-center">

            <Stream onAddTweetToCollection={this.addTweetToCollection} />

          </div>
          <div className="col-md-8">

            <Collection
              tweets={this.state.collectionTweets}
              onRemoveTweetFromCollection={this.removeTweetFromCollection}
              onRemoveAllTweetsFromCollection={this.removeAllTweetsFromCollection} />

          </div>
        </div>

      </div>
    );
  }
});

module.exports = Application;

This component has significantly more code than our app.js file, but this code can be easily divided into three logical parts:

  • Importing dependency modules
  • Defining React components
  • Exporting a React component as a module

You will see this logical separation in most of our React components because they are wrapped into the CommonJS module pattern that allows us to easily require them with Browserify. In fact, the first and the third parts of this source file are related to how CommonJS works and have nothing to do with how React works. The purpose of using this module pattern is to break our application into modules that can be easily reused. Because the React component and CommonJS module pattern both encapsulate the code and make it portable, they naturally work great together. So, we end up encapsulating our user interface logic in a React component and then encapsulate that React component in the CommonJS module. It then can be used in any other module that wants to reuse this encapsulated React component.

In our first logical part of the Application.react.js file, we're importing the dependency modules using the require() function:

var React = require('react'),
var Stream = require('./Stream.react'),
var Collection = require('./Collection.react'),

Our Application component will have two child components that we need to import:

  • The Stream component will render a stream section of our user interface
  • The Collection component will render a collection section of our user interface

We also need to import the React library as another module. Notice that this code is still part of the CommonJS module pattern, not React.

The second logical part of the Application.react.js file creates the React Application component with the following methods:

  • getInitialState()
  • addTweetToCollection()
  • removeTweetFromCollection()
  • removeAllTweetsFromCollection()
  • render()

Only the getInitialState() and render() methods are part of the React API. All the other methods are part of our application logic that this component encapsulates. We'll take a closer look at each of them right after we discuss what this component renders inside its render() function:

render: function () {
  return (
    <div className="container-fluid">

      <div className="row">
        <div className="col-md-4 text-center">

          <Stream onAddTweetToCollection={this.addTweetToCollection} />

        </div>
        <div className="col-md-8">

          <Collection
            tweets={this.state.collectionTweets}
           onRemoveTweetFromCollection={this.removeTweetFromCollection}
       onRemoveAllTweetsFromCollection={this.removeAllTweetsFromCollection} />

        </div>
      </div>

    </div>
  );
}

As you can see, it defines the layout of our web page using the Bootstrap framework. If you're not familiar with Bootstrap, I strongly recommend that you visit http://getbootstrap.com and read the documentation. Learning this framework will empower you to prototype user interfaces in a fast and easy way. Even if you don't know Bootstrap, it's quite easy to understand what's going on. We're dividing our web page into two columns: a smaller one and a larger one. The smaller one contains our Stream React component and the larger one contains our Collection component. You can imagine that our web page is divided into two unequal parts and both of them contain the React components.

This is how we're using our Stream component:

<Stream onAddTweetToCollection={this.addTweetToCollection} />

The Stream component has an onAddTweetToCollection property, and our Application component passes its own addTweetToCollection() function as a value for this property. addTweetToCollection()adds a tweet to a collection. It's one of the custom methods that we define in our Application component, and we can refer to it using this keyword.

Let's take a look at what the addTweetToCollection() function does:

addTweetToCollection: function (tweet) {
  var collectionTweets = this.state.collectionTweets;

  collectionTweets[tweet.id] = tweet;

  this.setState({
    collectionTweets: collectionTweets
  });
},

This function references CollectionTweets that are stored in the current state, adds a new tweet to a collectionTweets object, and updates the state by calling the setState() function. A new tweet is passed as an argument when the addTweetToCollection() function is called inside a Stream component. This is an example of how a child component can update its parent component's state.

This an important mechanism in React and it works as follows:

  1. A parent component passes a callback function as a property to its child component. A child component can access this callback function via the this.props variable.
  2. Whenever a child component wants to update the parent component's state, it calls that callback function and passes all the necessary data to a new parent component's state.
  3. A parent component updates its state, and as you already know, this state updates and triggers the render() function that re-renders all the child components, as necessary.

This is how a child component interacts with a parent component. This interaction allows a child component to delegate the application's state management to its parent component, and it is only concerned with how to render itself. Now, when you've learned this pattern, you will be using it again and again because most of your React components should stay stateless. Only a few parent components should store and manage your application's state. This best practice allows us to logically group React components by the two different concerns that they address:

  • Manage the application's state and render
  • Only render and delegate the application's state management to a parent component

Our Application component has a second child component, Collection:

<Collection
  tweets={this.state.collectionTweets}
  onRemoveTweetFromCollection={this.removeTweetFromCollection}
onRemoveAllTweetsFromCollection={this.removeAllTweetsFromCollection} />

This component has a number of properties:

  • tweets: This refers to our current collection of tweets
  • onRemoveTweetFromCollection: This refers to a function that removes a particular tweet from our collection
  • onRemoveAllTweetsFromCollection: This refers to a function that removes all the tweets from our collection

You can see that the Collection component's properties are only concerned about how to:

  • Access the application's state
  • Mutate the application's state

As you can guess, the onRemoveTweetFromCollection and onRemoveAllTweetsFromCollection functions allow the Collection component to mutate the Application component's state. On the other hand, the tweets property propagates the Application component's state to the Collection component so that it can gain a read-only access to that state.

Can you recognize the single direction of data flow between the Application and Collection components? Here's how it works:

  1. The collectionTweets data is initialized in the Application component's getInitialState() method.
  2. The collectionTweets data is passed to the Collection component as the tweets property.
  3. The Collection component calls the removeTweetFromCollection and removeAllTweetsFromCollection functions that update the collectionTweets data in the Application component, and the cycle starts again.

Notice that the Collection component cannot directly mutate the Application component's state. The Collection component has read-only access to that state via this.props object, and the only way to update the parent component's state is to call the callback functions that are passed by the parent component. In the Collection component, these callback functions are this.props.onRemoveTweetFromCollection and this.props.onRemoveAllTweetsFromCollection.

This simple mental model of how data flows in our React component hierarchy will help us increase the number of components we use, without increasing the complexity of how our user interface works. For example, it can have 10 levels of nested React components, as follows:

Creating a container React component

If Component G wants to mutate the state of root Component A, it would do it in the exact same way that Component B, or Component F, or any other component in this hierarchy would. However, in React, you shouldn't pass data from Component A directly to Component G. Instead, you should first pass it to Component B, then to Component C, then to Component D, and so on until you finally reach Component G. Component B to Component F will have to carry some "transit" properties that are actually only meant for Component G. This might look like a waste of time, but this design makes it easy for us to debug our application and be able to reason out how it works. There are always strategies to optimize your application's architecture. One of them is to use Flux, which we'll discuss later in this book.

Before we finish discussing our Application component, let's take a look at the two methods that mutate its state:

removeTweetFromCollection: function (tweet) {
  var collectionTweets = this.state.collectionTweets;

  delete collectionTweets[tweet.id];

  this.setState({
    collectionTweets: collectionTweets
  });
},

The removeTweetFromCollection() method removes a tweet from a collection of tweets that we store in the Application component's state. It takes the current collectionTweets object from the component's state, deletes a tweet with a given ID from that object, and updates the component's state with an updated collectionTweets object.

On the other hand, the removeAllTweetsFromCollection() method removes all the tweets from the component's state:

removeAllTweetsFromCollection: function () {
  this.setState({
    collectionTweets: {}
  });
},

Both of these methods are called from a child's Collection component because that component has no other way to mutate the Application component's state.

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

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