Motivating Higher-Order Components
- A higher-order component (HOC) are used for sharing common functionality between components
- Lifting the state up may achieve this goal as a side effect
- However, HOCs are geared more towards reusability
- Whereas, lifting state is more geared towards a component demonstrating behavior dependent on another component
Defining Higher-Order Components
- HOCs are not part of the React API
- Rather, they are a pattern that emerges from React’s compositional nature
- HOCs are functions that accepts a component and returns an enhanced component
- They are used to share common functionality between components without having to repeat code
- If a component is thought to transform props into UI, then an HOC transforms a component into another component
- At a high level, an HOC is a pattern where a function takes a component as an argument and returns a new component
- HOCs only add functionality to an already existing component
- As an analogy, its implementation is similar to the following:
const IronMan = withSuit(TonyStark);
Illustrating a Basic Higher-Order Component
- Again, an HOC is just a design pattern
- It isn't part of the React API
- Specifically, it refers to a standard function
- The following function is an example of an HOC:
// hoc.js
const hoc = WrappedComponent => {
class HOC extends Component {
render() {
return <WrappedComponent />
}
}
return HOC;
}
export default hoc;
// SomeComponent.js
import hoc from './hoc'
class SomeComponent extends Component {
render() {
return <h1>Hello World</h1>
}
}
export default hoc(SomeComponent)
Illustrating A Higher-Order Component with Props
- The HOC listed above isn't very interesting, since it doesn't include any additional functionality
- The following HOC has a prop tied to it:
const HOC = OriginalComponent => {
class NewComponent extends Component {
render() {
return <OriginalComponent name='Mike' />
}
}
return NewComponent;
}
Illustrating a Reusable Higher-Order Component
-
The component
WithCounter
maintains:- The state containing a
count
property - A method for incrementing the
count
property
- The state containing a
- Both the state and method are passed as props to the
ClickCounter
andHoverCounter
- The
HoverCounter
is returned with the enhanced properties - Then, the control returns to both the
ClickCounter
andHoverCounter
components - Here, the
count
prop andaddCount
method are passed in and destructured from the HOC - These are then rendered in both the
ClickCounter
andHoverCounter
components - Essentially, the HOC maintains the state and increments it
- However, both the
ClickCounter
andHoverCounter
components receive separate states - Meaning, incrementing the
ClickCounter
component will not affect theHoverCounter
component, and vice versa
// App.js
class App extends Component {
render() {
return(
<div className="App">
<ClickCounter />
<HoverCounter />
</div>
)
}
}
export default App;
// ClickCounter.js
class ClickCounter extends Component {
render() {
const { count, addCount } = this.props;
return (
<button onClick={addCount}>
Clicked {count} times
</button>
)
}
}
export default withCounter(ClickCounter);
// HoverCounter.js
class HoverCounter extends Component {
render() {
const { count, addCount } = this.props;
return (
<button onMouseOver={addCount}>
Clicked {count} times
</button>
)
}
}
export default withCounter(HoverCounter);
// withCounter.js
const withCounter = WrappedComponent => {
class WithCounter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
addCount = () => {
this.setState(prevState => {
return {count: prevState.count+1};
}
}
render() {
return (
<WrappedComponent
count={this.state.count}
addCount={this.addCount}
/>
)
}
}
return WithCounter;
}
export default withCounter;
Passing Properties between HOCs
- Properties passed to the
ClickCounter
component are passed toWithCounter
, but notWrappedComponent
- To fix this issue, the remaining props must be passed down to the
WrappedComponent
using the spread operator
// App.js
class App extends Component {
render() {
return(
<div className="App">
// this line has changed
<ClickCounter name='Mike' />
<HoverCounter />
</div>
)
}
}
// ClickCounter.js
class ClickCounter extends Component {
render() {
const { count, addCount } = this.props;
return (
<button onClick={addCount}>
// this line has changed
{this.props.name} Clicked {count} times
</button>
)
}
}
// withCounter.js
...
render() {
return (
// this line has changed
<WrappedComponent
count={this.state.count}
addCount={this.addCount}
{...this.props}
/>
)
}
...
Passing Parameters to the HOC
- Sometimes, we'll want to pass parameters to the HOC
- In our example, we may want to increment by a different value depending on the component that is passed to the HOC
- The following is an example of this:
// withCounter.js
const withCounter = (WrappedComponent, incNum) => {
...
addCount = () => {
this.setState(prevState => {
return {count: prevState.count + incNum}
}
}
}
// ClickCounter.js
...
export default withCounter(ClickCounter, 5);
// HoverCounter.js
...
export default withCounter(HoverCounter, 3);
References
Previous
Next