The HOC is a pattern that exists to enhance components with additional props or functionality, for instance, if you want to make the component expandable. Instead of just creating a stateful container as we did previously, we could use the HOC pattern. Let's refactor our stateful container component to a HOC and name it makeExpandable:
// src/ Chapter_1/ Example_12_Higher_order_component_makeExpandable/ App.js
const makeExpandable = (ComponentToEnrich) => (
class HelloBoxContainer extends React.Component {
constructor() {
super();
this.state = {
// default state on first render
expanded: false
};
this.expandOrCollapse = this.expandOrCollapse.bind(this);
}
expandOrCollapse() {
// toggle expanded: true becomes false, false becomes true
this.setState({expanded: !this.state.expanded});
}
render = () => (
<ComponentToEnrich
isExpanded={this.state.expanded}
expandOrCollapse={this.expandOrCollapse}
/>
);
}
);
The makeExpandable component accepts ComponentToEnrich. So, we can create a root component (App) like this:
export default makeExpandable(HelloBox);
Cool, isn't it? Now, let's create some other component and enrich it with our HOC. This will be a small button that displays the text hide or show. If the user presses the button, it should show or hide a small colored box. For this task, you can use the following styles:
box: {
width: 100,
height: 100,
backgroundColor: 'powderblue',
}
Place them within StyleSheet.create({ ... }). My solution is pretty straightforward:
// src/ Chapter_1/
// Example_13_Higher_order_component_show_hide_button/ App.js
export const SomeSection = ({
isExpanded,
expandOrCollapse,
containerStyles,
boxStyle
}) => (
<View style={containerStyles || styles.container}>
<Button
onPress={expandOrCollapse}
title={isExpanded ? "Hide" : "Show"}
color="#841584"
/>
{isExpanded && <View style={boxStyle || styles.box} />}
</View>
);
export default makeExpandable(SomeSection);
In the preceding example, the SomeSection component is wrapped by the makeExpandable HOC, and receives the isExpanded and expandOrCollapse props.
Great! We have just made a reusable HOC, and it is working flawlessly.
Now, I will show you a rather unknown but sometimes useful technique to push your HOC to be even more flexible. Imagine that you are about to enhance a component that is strict about props naming, as in the following example:
export const SomeSection = ({
showHideBox,
isVisible,
containerStyles,
boxStyle
}) => {...};
Unfortunately, our HOC, makeExpandable, is passing the wrong prop names. Let's fix that:
// src/ Chapter_1/ Example_14_Flexible_prop_names_in_HOC/ App.js
render = () => {
const props = {
[propNames && propNames.isExpanded || 'isExpanded']: this.state.expanded,
[propNames && propNames.expandOrCollapse || 'expandOrCollapse']: this.expandOrCollapse
};
return <ComponentToEnrich {...props} />
};
This is a tricky example. It provides a capability to rename props that are passed down by HOC. To rename it, we need to pass a configuration object called propNames to HOC. If such an object is passed, and it contains a certain key, then we override the name. If the key is not present, then we fall back to the default prop name, for instance, isExpanded.
Notice the use of [] inside of the object. It allows you to dynamically name keys in the object. In this example, the key was dynamically chosen based on the presence of propNames.
To make everything work, we also need to accept the optional argument propNames in the makeExpandable HOC:
const makeExpandable = (ComponentToEnrich, propNames) => (
...
)
Cool! Now our HOC is more flexible when it comes to prop names! We can use it with the aforementioned strict SomeSection component:
export default makeExpandable(SomeSection, {
isExpanded: 'isVisible',
expandOrCollapse: 'showHideBox'
});
Beware of the performance implications when creating variables inside the render function. It will slow your application down. Sometimes, patterns can sacrifice performance a little and sometimes they cannot. Use them wisely. You could also the inline propNames variable as two props.
Make sure to check the next section for a cleaner and decoupled approach.