Container components
In this final section of the chapter, we're going to cover the concept of container components. This is a common React pattern, and it brings together many of the concepts that you've learned about state and properties.
The basic premise of container components is simple: don't couple data fetching with the component that renders the data. The container is responsible for fetching the data and passing it to its child component. It contains the component responsible for rendering the data.
The idea is that you should be able to achieve some level of substitutability with this pattern. For example, a container could substitute its child component. Or, a child component could be used in a different container. Let's see the container pattern in action, starting with the container itself:
import React, { Component } from 'react'; import MyList from './MyList'; // Utility function that's intended to mock // a service that this component uses to // fetch it's data. It returns a promise, just // like a real async API call would. In this case, // the data is resolved after a 2 second delay. function fetchData() { return new Promise((resolve) => { setTimeout(() => { resolve([ 'First', 'Second', 'Third', ]); }, 2000); }); } // Container components usually have state, so they // can't be declared as functions. export default class MyContainer extends Component { // The container should always have an initial state, // since this will be passed down to child components // as properties. state = { items: [] } // After the component has been rendered, make the // call to fetch the component data, and change the // state when the data arrives. componentDidMount() { fetchData() .then(items => this.setState({ items })); } // Renders the containee, passing the container // state as properties, using the spread operator: "...". render() { return ( <MyList {...this.state} /> ); } }
The job of this component is to fetch data and to set its state. Any time the state is set, render()
is called. This is where the child component comes in. The state of the container is passed to the child as properties. Let's take a look at the MyList
component next:
import React from 'react'; // A stateless component that expects // an "items" property so that it can render // a "<ul>" element. export default ({ items }) => ( <ul> {items.map(i => ( <li key={i}>{i}</li> ))} </ul> );
Nothing much to it; a simple functional component that expects an items
property. Let's see how the container component is actually used:
import React from 'react'; import { render } from 'react-dom'; import MyContainer from './MyContainer'; // All we have to do is render the "MyContainer" // component, since it looks after providing props // for it's children. render( (<MyContainer />), document.getElementById('app') );
We'll go into more depth on container component design in Chapter 5, Crafting Reusable Components. The idea of this example was to give you a feel for the interplay between state and properties in React components.
When you load the page, you'll see the following content rendered after the 3 seconds it takes to simulate an HTTP request: