Background
In software construction, design pattern is a common approach when designing your program. Besides making the design process more efficient, using design pattern to design your program also allow you to determine the tradeoff of a particular design, as the pros and cons of design patterns are studied.
Compared to OOP in which the well-known design patterns are documented clearly by the GOF book, the design patterns for React component are still in flux. However, there are a few patterns that had emerged and employed by many React developers today, i.e.:
- Container-presenter
- Compound component
- Higher order component
- Render props
This article aims to explain the container-presenter pattern.
Problem
When we’re writing a React component, other than writing how the UI looks like with the JSX, we’re also concerned with:
- event handler
- data transformation/mapping/mutation
- states
When we’re mixing the UI concern with all other code, it would makes the component harder to understand and reason with.
Container-presenter
Container-presenter pattern means your React component should only be either concerned with UI (presenter) or other (container), but not both.
A presenter:
- concerned with how things look.
- usually have some DOM markup and styles or class as styling hook.
- don’t specify how the data is loaded, transformed, or mutated.
- is usually written as functional component.
- have no dependency on the rest of the app, e.g. Flux actions or redux.
A container:
- concerned with how things work.
- usually no DOM markup, except wrapper
<div>
. - provide the data and behavior to the child components.
Examples
(Shamelessly steal and modified based on Michael Chan’s gist)
import * as React from 'react';
const Commentlist = ({ comments, isLoading }) => (
<ul>
{isLoading && (
<li>
<span>Loading...</span>
</li>
)}
{comments.map(({ body, author }) => (
<li>
{body}-{author}
</li>
))}
</ul>
);
import * as React from 'react';
import CommentList from './CommentList';
class CommentListContainer extends React.Component {
constructor() {
super();
this.state = { comments: [], isLoading: true };
}
componentDidMount() {
fetch('/my-comments.json')
.then((res) => res.json())
.then((comments) => this.setState({ comments, isLoading: false }));
}
render() {
return <CommentList comments={this.state.comments} isLoading={this.state.isLoading} />;
}
}
Use with other patterns
Component that use render props pattern fully and do not render any DOM by itself should be placed in Container component as those component is concerned with behavior/states only.