Real world Higher-Order Components (HOCs)
Trying to wrap your head around React HOCs? Want some real world examples to look at?
Then you’ve come to the right place!
If you are completely new to React HOCs, you should read my Simple explanation of Higher-Order Components (HOC) before moving on.
Add a hidden prop to your components
This HOC adds a hidden
prop to your components. When hidden
is set to true, your components becomes… hidden!
Usage
const HelloWorldWithHidden = withHiddenProp(HelloWorld)
// ...
<HelloWorldWithHidden hidden={true} />
HOC
export function withHiddenProp(WrappedComponent) {
return class extends React.Component {
render() {
if (this.props.hidden) {
return null
} else {
return <WrappedComponent {...this.props} />
}
}
}
}
Full example
Full example on codesandbox.io
Add toggle functionality to your components
This HOC adds two props to your components: toggled
and onToggle
toggled
: a boolean that represents the toggleonToggle
: a function that toggles the toggled variable
Usage
const Button = ({ toggled, onToggle }) => (
<button onClick={onToggle}>{toggled ? "It's ON! :)" : "It's OFF! :("}</button>
)
const ToggledButton = withToggle(Button)
// ...
<ToggledButton />
HOC
function withToggle(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props)
this.onToggle = this.onToggle.bind(this)
this.state = {
toggled: false,
}
}
onToggle() {
this.setState({ toggled: !this.state.toggled })
}
render() {
return (
<WrappedComponent
onToggle={this.onToggle}
toggled={this.state.toggled}
{...this.props}
/>
)
}
}
}
Full example
Full example on codesandbox.io
Show component when feature toggle is set
Feature toggles are a powerful technique to change behavior without deploying new code.
When using this HOC, your component will only be displayed when its feature toggle is set to true.
Usage
const HelloWorldWithToggle = whenFeatureToggled("helloWorld", HelloWorld)
// ...
<HelloWorldWithToggle />
HOC
// This might come from a global config file
const featureToggles = {
helloWorld: true
};
function whenFeatureToggled(feature, WrappedComponent) {
return class extends React.Component {
render() {
if (!featureToggles[feature]) {
return null;
} else {
return <WrappedComponent {...this.props} />;
}
}
};
Full example
Full example on codesandbox.io
Add a loading spinner while fetching data
A common UI pattern is showing a loading spinner while data is fetched asynchronously from the backend.
This HOC shows a spinner when this.props.data
is null
or undefined
.
Usage
// Presentational component
const HelloWorldWithLoadingSpinner = withLoadingSpinner(HelloWorld)
// Conatiner component
class HelloWorldContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
data: null,
}
// In the real world, this is probably an Ajax call
setTimeout(() => this.loadingComplete(), 2000)
}
loadingComplete() {
this.setState({ data: "hello" })
}
render() {
return <HelloWorldWithLoadingSpinner data={this.state.data} />
}
}
HOC
function withLoadingSpinner(WrappedComponent) {
return class extends React.Component {
render() {
if (!this.props.data) {
return <Spinner />
} else {
return <WrappedComponent {...this.props} />
}
}
}
}
Full example
Full example on codesandbox.io
Responsive components
If you need a responsive design you have two options: either use media queries with CSS or use JavaScript/React. There are pro and cons for each. If you go for the React solution you might want to use this HOC.
This HOC is based on this excellent solution for detecting mobile/desktop by Gosha Arinich
Usage
class HelloWorld extends React.Component {
render() {
return this.props.isDesktop ? (
<h1>Hello world from Desktop</h1>
) : (
<h1>Hello world from mobile</h1>
)
}
}
const HelloWorldResponsive = detectDesktopOrMobile(HelloWorld)
// ...
<HelloWorldResponsive />
HOC
function detectDesktopOrMobile(WrappedComponent) {
return class extends React.Component {
constructor() {
super()
this.state = {
width: window.innerWidth,
}
}
componentWillMount() {
window.addEventListener("resize", this.handleWindowSizeChange)
}
// make sure to remove the listener
// when the component is not mounted anymore
componentWillUnmount() {
window.removeEventListener("resize", this.handleWindowSizeChange)
}
handleWindowSizeChange = () => {
this.setState({ width: window.innerWidth })
}
render() {
return (
<WrappedComponent isDesktop={this.state.width > 500} {...this.props} />
)
}
}
}
Full example
Full example on codesandbox.io
Combining HOCs
Now we have a bunch of small composable HOCs. We can combine many HOCs on one component like this:
const Button = ({ toggled, onToggle }) => (
<button onClick={onToggle}>{toggled ? "It's ON! :)" : "It's OFF! :("}</button>
)
// Show a toggle button when it's feature toggled in
const ToggledButton = whenFeatureToggled("myToggleButton", withToggle(Button))
// ...
<ToggledButton />
As you can see, you combine HOCs by doing nested function calls. This can be a bit tricky to read and work with when having many HOCs:
hoc1(hoc2(hoc3(hoc4(Component))))
A solution to this is using a utility library included in Redux (and Recompose) that composes HOCs:
compose(hoc1, hoc2, hoc3, hoc4)(Component)
More readable and easier to work with!