React Higher Order Components in 3 Minutes
Grasp this useful pattern to stop repeating logic in React Components! 😎
What? permalink
A pattern for when we find ourselves repeating logic across components. They are not part of the React
API.
What? permalink
They are functions that take components and return new components!
When? permalink
When you’re repeating patterns/logic across components.
Examples;
- Hooking into/subscribing to a data source
- Adding interactivity to UI (also achieved with wrapper/render props)
- Sorting/filtering input data
A Silly Example permalink
We have a Mouse
component.
const Mouse = () => (
<span className="mouse" role="img">🐭</span>
)
Let’s make it Draggable
using GreenSock
’s Draggable
module.
class Mouse extends Component {
componentDidMount = () => new Draggable(this.ELEMENT)
render = () => (
<span className="mouse" role="img" ref={e => (this.ELEMENT = e)}>🐭</span>
)
}
We add Cat
😺
const Cat = () => (
<span className="cat" role="img">😺</span>
)
That needs to be Draggable
too ✋ Opportunity for a Higher Order Component(HOC).
const withDrag = Wrapped => {
class WithDrag extends Component {
componentDidMount = () => new Draggable(this.ELEMENT)
render = () => {
return (
<span className="draggable__wrapper" ref={e => (this.ELEMENT = e)}>
<Wrapped {...this.props} />
</span>
)
}
}
WithDrag.displayName = `WithDrag(${Wrapped.displayName || Wrapped.name})`
return WithDrag
}
Our HOC takes a Component and returns a new Component passing props
.
Many HOCs inject new props alongside those passed through. That is often an indicator of whether a HOC is suitable. If we aren’t injecting props
, we could likely use a wrapper component or render
props.
For our HOC, we could achieve the same result with render
props. You could argue that a HOC is not suitable. But this is a “silly” example to get you familiar with looking at HOCs. It’s a little more fun than looking at hooking into a data source too! 😉
Let’s apply this HOC to Cat
and Mouse
👍
const Mouse = () => (
<span className="mouse" role="img">🐭</span>
)
const Cat = () => (
<span className="cat" role="img">😸</span>
)
const DraggableMouse = withDrag(Mouse)
const DraggableCat = withDrag(Cat)
class App extends Component {
render = () => (
<Fragment>
<DraggableCat />
<DraggableMouse />
</Fragment>
)
}
Let’s hook into the onDrag
callback and inject the x
and y
position as props
.
const withDrag = Wrapped => {
class WithDrag extends Component {
componentDidMount = () => new Draggable(this.ELEMENT, { onDrag: this.onDrag })
onDrag = e => {
const { x, y } = e.target.getBoundingClientRect()
this.setState({ x: Math.floor(x), y: Math.floor(y)})
}
render = () => {
return (
<span className="draggable__wrapper" ref={e => (this.ELEMENT = e)}>
<Wrapped {...this.props} x={this.state.x} y={this.state.y} />
</span>
)
}
}
WithDrag.displayName = `WithDrag(${Wrapped.displayName || Wrapped.name})`
return WithDrag
}
const Mouse = ({ x, y }) => (
<span className="mouse" role="img">
🐭
{x !== undefined && y !== undefined && (
<span className="mouse__position">{`(${x}, ${y})`}</span>
)}
</span>
)
Now the mouse shows its XY position to the user 🤓
We can also pass props
to the HOC. And then filter these out before passing through. For example, passing an onDrag
callback.
const withDrag = Wrapped => {
class WithDrag extends Component {
componentDidMount = () => new Draggable(this.ELEMENT, { onDrag: this.props.onDrag })
render = () => {
const { onDrag, ...passed } = this.props
return (
<span className="draggable__wrapper" ref={e => (this.ELEMENT = e)}>
<Wrapped {...passed} />
</span>
)
}
}
WithDrag.displayName = `WithDrag(${Wrapped.displayName || Wrapped.name})`
return WithDrag
}
class App extends Component {
render = () => (
<Fragment>
<DraggableMouse onDrag={e => console.info(e.target.getBoundingClientRect())}>
</Fragment>
)
}
By using a HOC, our components remain simple and the logic remains in one place. Our components only care about what is being passed to them. We can reuse them elsewhere without them being draggable. This makes things easier to maintain.
DO’s 👍 permalink
- Use when there’s an opportunity to package up a repeating pattern
- Make debugging easier by updating the
displayName
of the returnedComponent
- Pass through
props
unrelated to the HOC
DON’Ts 👎 permalink
- Overuse. Another pattern may be more appropriate.
- Mutate the original
Component
- Use inside the
render
method
NOTES ⚠️ permalink
ref
s aren’t passed throughstatic
methods must be copied over- Most HOCs can be written with
render
props and vice versa!
That’s it! permalink
This is a super small intro to Higher Order Components.