In this section we are going to cover a few design patterns that people found useful when implementing their React components.
The patterns below may look different, but they all follow one common principle behind their implementation details, which is Separation of Concerns. Following this principle, each React component should just focus on one thing and we can compose them to achieve our final goal.
For example, there could be a React component that retrieves data from a web API and stores the data in its local state. And there could be another component that knows how to display those data. We can compose these two components into one that does both data retrieval and display. As you will see below, there are three different ways to compose the two components:
- Container/Presenter pattern
- Higher Order Component (HOC) pattern
- Render Props pattern
These topics are a bit abstract, and the best way to understand them is look at a sample use case. This article provides an excellent introduction on these three patterns. Read it before you read the (more abstract) description below. (BTW, the sample codes for the "Provider Pattern" in the article does not work anymore because React has changed its Context API since the article was written. However, the idea is still valid and you can achieve that with the latest React Context API as well.)
There is also a video made by the same author.
In React 16.8.0 released in Feb 2019, a new feature called React Hooks was added. With React hooks, the component that retrieves data from web API can be implemented as a hook and re-used by any other component that needs those data. That (sort of) makes the patterns discussed in this section obsolete. However, you still need to be aware of these patterns when you deal with legacy codes that were written before the days of React Hooks. And in some cases, the High Order Component pattern and Render Props pattern are still easier to use than React Hooks. So you will continue to see them around.
The first technique we are going to discuss is to separate components into 'Presentational' components and 'Container' components. The 'Presentational' component represents the UI elements based on the props passed to it, and 'Container' components focus on obtaining the data to be passed into the 'Presentational' component.
The idea is explained well in this article
Dan Abramov also summarized benefit of this technique in one of his blog post
In 'Container' components, you prepare the data and callbacks for your presenter components. Container components are most of the time ES6 class components which handle lifecycle methods or manage local state. Typically the 'Container' components need to obtain the data by calling some web API.
In 'Presentational' components, you should avoid adding any logic that are not related to UI presentation. Keep your components dumb and only pass properties and callbacks to them. These components should most of the time be functional, stateless components. You can keep them pure and remove any side effects. A pure component means that the view will be always the same when using the same props as input.
Here are a few more good articles to help you understand the concept:
A higher-order component (HOC) is an advanced technique in React for reusing component logic. Higher-order components are not part of the React API. They are a pattern that emerges from React’s compositional nature.
A higher-order component is a function that takes in a React component as input or returns a new React component as output. Whereas a regular React component transforms props into React element, a higher-order React component transforms one React component into another React component.
Note that a HOC doesn’t modify the input component, nor does it use inheritance to copy its behavior. Rather, a HOC composes the original component by wrapping it in a container component. A HOC is a pure function with zero side-effects.
The wrapped component receives props from the higher order component, which receives the props from its parent component. In many cases, the HOC component just pass the props it receives to the wrapped component (together with some new props generated by the HOC itself). The HOC isn’t concerned with how the props are used, and the wrapped component isn’t concerned with where the props come from.
One use case of HOC is to add new functionality without modifying existing component. This follows the Open Close Principle.
Imagine you want to display a list of items, but you have to fetch the items asynchronously first. Now you will need a loading indicator to show your pending request. After the request resolves, you show the list of items.
Suppose you already have a component called ListItems
that display the list of items, how can you add this new functionality to display a loading indicator before the items are ready to be displayed?
Without modifying the existing ListItem
component, you could solve this problem with a HOC withLoaingSpinner
, like the example below.
function withLoadingSpinner(Component) {
return ({ isLoading, ...props }) => {
if (!isLoading) {
return <Component { ...props } />;
}
return <LoadingSpinner />;
};
}
// you can make more fancy Spinner UI, but here is a basic one
const LoadingSpinner = () => return <div>Loading...</div>;
Here is how you can use this HOC:
const ListItemsWithLoadingIndicator = withLoadingSpinner(ListItems);
<ListItemsWithLoadingIndicator
isLoading={props.isLoading}
list={props.list}
/>
Note that this withLoadingSpinner
component can be re-used with any component.
Another example of HOC is to handle cross-cutting concerns such as logging.
The following HOC logs the props passed to wrapped component:
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
}
Again, note that this logProps
HOC can be used with any component.
Another example of addressing cross-cutting concerns with HOC is given in the official React documentation
Here are a few more good articles on this topic:
There is also a good library that implements lots of useful higher order components:
The term “render prop” refers to a simple technique for sharing code between React components using a prop whose value is a function.
A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.
<DataFetcher render={data => (
<h1>Hello {data.target}</h1>
)}/>
Reference: https://reactjs.org/docs/render-props.html
One example is given here
Some argues that render props are a more powerful pattern than HOCs is the fact that any HOC can be implemented using a render prop, but the inverse is not true. More details can be found in this article
- React Component Patterns
- React Patterns
- Another talk on React Component Patterns
- Advanced React.js course
- Some components using the render-props technique
Refactor your codes for the fetch-react-lab to apply two design patterns:
- The
ProfilesPage
component should be a Presentational component, i.e. it should not contain any logic for retrieving the data. Instead, those data fetching logic should be moved to a new Container component calledProfileFetcher
. TheProfileFetcher
component should contain theProfilePage
component. - Implement a new feature: display a loading spinner before profile data is retrieved from the backend API. You need to implement the loading spinner as a higher order component. To display the spinner on UI, you can use this react-spinkit library or react-spinners
- Try to implement the loading spinner component using render-props approach instead of higher-order-component-approach. Compare the two approaches, which one do you like?