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
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 you can click) directly to identify problems:
// Presentational Component
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>
<button onClick={this.toggleActive}>Toggle Active</button>
</li>
);
})}
</ul>
);
},
toggleActive: function() {
// We shouldn't be changing state in presentational components :(
}
});
This would technically work, but it’s not a good idea. Chances are, the event is going to need to change data, and data which changes should be stored as state — something the Presentational Component should not be aware of.
In our example, the state change would be the user’s “activeness”, but you can make up any functions you want to tie to onClick
.
A better solution is to pass functionality from the Container Component into the Presentational Component as a prop like this:
// Container Component
var UserListContainer = React.createClass({
...
render: function() {
return (<UserList users={this.state.users} toggleActive={this.toggleActive} />);
},
toggleActive: function() {
// We should change state in container components :)
}
});
// Presentational Component
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>
<button onClick={this.props.toggleActive}>Toggle Active</button>
</li>
);
})}
</ul>
);
}
});
The onClick
attribute is required to be where the view is — at the Presentational Component. However, the function it calls has been moved to the parent Container Component. This is better because the Container Component deals with state.
If the parent function happens to change the state, then the state change will cause a re-render on the parent function which in turn will update the child component. This happens automatically in React.
Here is a demo which shows how the event on the Container Component can change state which will automatically update the Presentational Component:
See the Pen React Container Component Demo by Brad Westfall (@bradwestfall) on CodePen.
Take note of how this example deals with immutable data and makes use of the .bind() method
Using Container Components with the Router
The router should no longer use UserList
directly. Instead, it will use UserListContainer
directly, which in turn will use UserList
. Ultimately, UserListContainer
returns the result of UserList
, so the router will still receive what it needs.
Data Flow and the Spread Operator
In React, the concept of props being passed from parent components down to child components is called flow. The examples so far have only shown simple parent-child relationships, but in a real application there could be many nested components. Imagine data flowing from high-level parent components down through many child components via state and props. This is a fundamental concept in React and is important to keep in mind as we move forward into the next tutorial on Redux.
ES6 has a new spread operator which is very useful. React has adopted a similar syntax to JSX. This really helps React with how data flows via props. The GitHub guide for this tutorial uses it too, so be sure to read the guide documentation on this feature.
Stateless Functional Components
As of React 0.14 (released in late 2015), there is a new feature for creating stateless (Presentational) components even easier. The new feature is called Stateless Functional Components
By now you’ve probably noticed that as you separate your Container and Presentational Components, many of your Presentational ones just have a render method. In these cases, React now allows the component to be written as a single function:
// The older, more verbose way
var Component = React.createClass({
render: function() {
return (
<div>{this.props.foo}</div>
);
}
});
// The newer "Stateless Functional Component" way
var Component = function(props) {
return (
<div>{props.foo}</div>
);
};
You can clearly see that the new way is more compact. But remember, this is only an option for components that just need a render
method.
With the new Stateless Functional way, the function accepts an argument for props
. This means it doesn’t need to use this
to access props.
Here is a very good Egghead.io video on Stateless Functional Components.
MVC
As you’ve probably seen so far, React doesn’t resemble traditional MVC. Often times React is referred to as “just the view layer”. The problem I have with this statement is that it’s too easy for React beginners to believe that React should fit into their familiarity with traditional MVC, as if it’s meant to to be used with traditional controllers and models brought in from third-party.
While it is true that React doesn’t have “traditional controllers”, it does provide a means to separate views and behavior in its own special way. I believe that Container Components serve the same fundamental purpose that a controller would serve in traditional MVC.
As for models, I’ve seen people use Backbone models with React and I’m sure they have all kinds of opinions on whether that worked out well for them. But I’m not convinced that traditional models are the way to go in
React. React wants to flow data in a way that just doesn’t lend itself well to how traditional models work. The Flux design pattern, created by Facebook, is a way to embrace React’s innate ability to flow data. In the next tutorial, we’ll cover Redux, a very popular Flux implementation and what I view as an alternative to traditional models.
Summary
Container Components are more of a concept and not an exact solution. The examples in this tutorial just are one way to do them. The concept though, is so well accepted that it’s even Facebook’s policy to use them within their teams — although they might use different terminology.
This tutorial was highly influenced by other articles on this topic. Be sure to look at this tutorial’s official GitHub guides for even more information and a working example of Container Components.
Article Series
Part 1: React Router
Part 2: Container Components (You are here!)
Part 3: Redux (Coming soon!)
Leveling Up With React: Container Components is a post from CSS-Tricks