Higher order components in React
A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOC is not part of the React API. It is a pattern that emerges from React’s compositional nature and that can be reused in different programming languages as well.
Concretely, a higher-order component is a function that takes a component and returns a new component.
const CoolerComponent = higherOrderComponent(NotAsCoolComponent);
Let’s say you have a component that fetches and shows a list of dogs from some API source:
class DogsList extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
// "APIDataSource" is some global api data source
dogs: APIDataSource.getDogs()
};
}
componentDidMount() {
APIDataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
APIDataSource.removeChangeListener(this.handleChange);
}
handleChange() {
// Update component state whenever the data source changes
this.setState({
dogs: APIDataSource.getDogs()
});
}
render() {
return (
<div>
{this.state.dogs.map((dog) => (
<Dog dog={dog} key={dog.id} />
))}
</div>
);
}
}
Later you need to create a component that fetches a cute cat from API and shows it in a text block:
class CuteCat extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
cuteCat: APIDataSource.getCuteCat(props.id)
};
}
componentDidMount() {
APIDataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
APIDataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
cuteCat: APIDataSource.getCuteCat(this.props.id)
});
}
render() {
return <TextBlock text={this.state.cuteCat} />;
}
}
DogsList
and CuteCat
aren’t identical — they call different methods on APIDataSource
, and they render different JSX. However, some of their functionalities overlap:
- On mount, add a change listener to
APIDataSource
. - Inside the listener, call
setState
whenever the data source changes. - On unmount, remove the change listener.
We can write a function that creates components, like DogsList
and CuteCat
, that subscribe to APIDataSource
. The function will accept as one of its arguments a child component that receives the subscribed data as a prop. Let’s call the function withDataSubscription
:
const DogsListWithSubscription = withDataSubscription(
DogsList,
(APIDataSource) => APIDataSource.getDogs()
);
const CuteCatWithSubscription = withDataSubscription(
CuteCat,
(APIDataSource, props) => APIDataSource.getCuteCat(props.id)
);function withDataSubscription(WrappedComponent, selectData) {
// returns another component
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(APIDataSource, props)
};
}
componentDidMount() {
// ... that takes care of the subscription...
APIDataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
APIDataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(APIDataSource, this.props)
});
}
render() {
// Renders the wrapped component with data
// We pass through any additional props
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
And that’s it! Go on to my other article to find out HOCs I use the most.