Home > Designing, Others > Leveling Up With React: Container Components

Leveling Up With React: Container Components

This tutorial is the second of a three-part series on React by Brad Westfall. This series is all about going beyond basic React skills and building bigger things, like entire Single Page Applications (SPAs). This article picks up where the last article, on React Router, left off.

Article Series

Part 1: React Router
Part 2: Container Components (You are here!)
Part 3: Redux (Coming soon!)

In the first article, we created routes and views. In this tutorial, we’re going to explore a new concept in which components don’t create views, but rather facilitate the ones that do. There is code to follow along with at GitHub, if you like to dive right into code.

We’ll also be introducing data to our application. If you’re familiar with with any sort of component-design or MVC patterns, you probably know that it’s generally considered poor-practice to mix your views with application behavior. In other words, while views need to receive data to render it, they shouldn’t know where the data came from, how it changes, or how to create it.

Fetching data with Ajax

As an example of a poor practice, let’s expand on our UserList component from the previous tutorial to handle its own data fetching:

// This is an example of tightly coupled view and data which we do not recommend

var UserList = React.createClass({
  getInitialState: function() {
    return {
      users: []
    }
  },

  componentDidMount: function() {
    var _this = this;
    $.get('/path/to/user-api').then(function(response) {
      _this.setState({users: response})
    });
  },

  render: function() {
    return (
      <ul className="user-list">
        {this.state.users.map(function(user) {
          return (
            <li key={user.id}>
              <Link to="{'/users/' + user.id}">{user.name}</Link>
            </li>
          );
        })}
      </ul>
    );
  }
});

If you need a more detailed/beginner explanation of what this component is doing, see this explanation.

Why is the example less than ideal?. To start, we’ve broken the rule of mixing “behavior” with “how to render the view” — the two things that should stay separate.

To be clear, there’s nothing wrong with using getInitialState to initialize component state, and there’s nothing wrong with conducting an Ajax request from componentDidMount (although we should probably abstract the actual call out to other functions). The problem is that we’re doing these things together in the same component where the view is stored. This tight-coupling makes the application more rigid and WET. What if you need to fetch a list of users elsewhere too? The action of fetching users is tied down to this view, so it’s not reusable.

The second problem is that we’re using jQuery for the Ajax call. Sure, jQuery has many nice features, but most of them deal with DOM rendering and React has its own way of doing this. As for jQuery’s non-DOM features like Ajax, chances are you can find lots of alternatives that are more single-feature focused.

One of those alternatives is Axios, a promise-based Ajax tool that’s very similar (in terms of API) to jQuery’s promise-based Ajax features. How similar are they?

// jQuery
$.get('/path/to/user-api').then(function(response) { ... });

// Axios
axios.get('/path/to/user-api').then(function(response) { ... });

For the remaining examples, we’ll continue to use Axios. Other similar tools include got, fetch, and SuperAgent.

Props and State

Before we get into Container and Presentational Components, we need to clear up something about props and state.

Props and state are somewhat related in the sense that they both “model” data for React components. Both of them can be passed down from parent to child components. However, the props and state of a parent component will become just props to their child component.

As an example, let’s say ComponentA passes some of its props and state to its child, ComponentB. The render method of ComponentA might look like this:

// ComponentA
render: function() {
  return <ComponentB foo={this.state.foo} bar={this.props.bar} />
}

Even though foo is “state” on the parent, it will become a “prop” on the child ComponentB. The attribute for bar also becomes a prop on the child component because all data passed from parent to child will become props in the child. This example shows how a method from ComponentB can access foo and bar as props:

// ComponentB
componentDidMount: function() {
  console.log(this.props.foo);
  console.log(this.props.bar);
}

In the Fetching Data with Ajax example, data received from Ajax is set as the component’s state. The example doesn’t have child components, but you can imagine if it did, the state would “flow” down from parent to child as props.

To understand state better, see the React Documentation. From here on, this tutorial will refer to the data that changes over time, as “state”.

It’s time to break up

In the Fetching Data with Ajax example, we created a problem. Our UserList component works but it’s trying to do too many things. To solve the problem, let’s break the UserList into two components that each serve a different role. The two component types will be conceptually called Container Components and Presentational Components, a.k.a. “smart” and “dumb” components.

In short, Container Components source the data and deal with state. State is then passed to Presentational Components as props and is then rendered into views.

The terms “start” vs “dumb” components are going away in the community. I’m only making reference to them here in case you read about them in older articles, so you’ll know they’re the same concept as Container vs Presentational.

Presentational Components

You may not know it, but you’ve already seen Presentation Components before in this tutorial series. Just imagine how the UserList component looked before it was managing its own state:

var UserList = React.createClass({
  render: function() {
    return (
      <ul className="user-list">
        {this.props.users.map(function(user) {
          return (
            <li key={user.id}>
              <Link to="{'/users/' + user.id}">{user.name}</Link>
            </li>
          );
        })}
      </ul>
    );
  }
});

It’s not exactly the same as before, but it is a Presentational Component. The big difference between it and the original is that this one iterates over user data to create list-items, and receives the user data via props.

Presentational Components are “dumb” in the sense that they have no idea how the props they received came to be. They have no idea about state.

Presentational Components should never change the prop data itself. In fact, any component that receives props should consider that data immutable and owned by the parent. While the Presentational Component shouldn’t change the meaningfulness of the data in the prop, it can format the data for the view (such as turning a Unix timestamp into a something more human readable).

In React, events are attached directly to the view with attributes like onClick. However, one might wonder how events work since Presentational Components aren’t supposed to change the props. For that, we have a whole section on events below.

Iterations

When creating DOM nodes in a loop, the key attribute is required to be something unique (relative to its siblings). Note that this is only for the highest level DOM node — the

  • in this case.

    Also, if the nested return looks funky to you, consider another approach which does the same thing by splitting the creation of a list item into its own function:

    var UserList = React.createClass({
      render: function() {
        return (
          <ul className="user-list">
            {this.props.users.map(this.createListItem)}
          </ul>
        );
      },
    
      createListItem: function(user) {
        return (
          <li key={user.id}>
            <Link to="{'/users/' + user.id}">{user.name}</Link>
          </li>
        );
      }
    });

    Container Components

    Container Components are almost always the parents of Presentational Components. In a way, they serve as a intermediary between Presentational Components and the rest of the application. They’re also called “smart” components because they’re aware of the application as a whole.

    Since Container and Presentational Components need to have different names, we’ll call this one UserListContainer to avoid confusion:

    var React = require('react');
    var axios = require('axios');
    var UserList = require('../views/list-user');
    
    var UserListContainer = React.createClass({
      getInitialState: function() {
        return {
          users: []
        }
      },
    
      componentDidMount: function() {
        var _this = this;
        axios.get('/path/to/user-api').then(function(response) {
          _this.setState({users: response.data})
        });
      },
    
      render: function() {
        return (<UserList users={this.state.users} />);
      }
    });
    
    module.exports = UserListContainer;

    For brevity, these examples have been leaving out require() and module.exports statements. But in this case, it’s important to show that Container Components pull in their respective Presentational Components as a direct dependency. For completeness, this example shows all the requires which would be necessary.

    Container Components can be created just like any other React component. They also have a render methods just like any other component, they just don’t create anything to render themselves. Instead, they return the result of the Presentational Component.

    A quick note on ES6 Arrow Functions: You may notice the classic var _this = this trick needed for the example above. ES6 Arrow functions, besides having shorter syntax, have other benefits which alleviate the need for this trick. To allow you to focus on just learning React, this tutorial avoids ES6 syntax in favor of the older ES5 syntax. However, the GitHub guide for this series makes heavy use of ES6 and it has some explanations in its README files.

    Events

    So far, we’ve shown how state can be passed from Container to Presentational Components, but what about behavior? Events fall into the category of behavior and they oftentimes need to mutate data. Events in React are attached at the view level. For separation of concerns, this can cause a problem in our Presentational Components if we create event functions where the view is.

    To elaborate, let’s start by adding an event to our Presentational Component (a

  • Categories: Designing, Others Tags:
    1. No comments yet.
    1. No trackbacks yet.
    You must be logged in to post a comment.