# Higher Order Components
Official docs (opens new window)
A Higher Order Component is a function that takes a Component and returns a new Component
WARNING
It's not a React feature or any other programming language, but a pattern evolved from compositional nature of react.
Lets say we are in charge of recreating a Dashborard similar to the one from the gif below. And it has a bunch of tooltips that needs to appear when certain elements are hovered over.
There are a few ways to approach this. The one you decide to go with is to detect the hover state of the individual components and from that state, show or not show the tooltip. There are three components you need to add this hover detection functionality to - Info, TrendChart and DailyChart.
Let’s start with Info. Right now it’s just a simple SVG icon.
const Info = () => (
<svg
className="Icon-svg Icon--hoverable-svg"
height={this.props.height}
viewBox="0 0 16 16"
width="16"
>
<path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />
</svg>
);
Now we need to add functionality to it so it can detect whether it's being hovered over or not. We can use the onMouseOver and onMouseOut mouse events that come with React. The function we pass to onMouseOver will be invoked when the component is hovered over and the function we pass to onMouseOut will be invoked when the component is no longer being hovered over. To do this the React way, we'll add a hovering state property to our component so that we can cause a re-render when the hovering state changes, showing or hiding our tooltip.
const Info = () => {
const [hovering, setHovering] = useState(false);
const onMouseOver = () => setHovering(true);
const onMouseOut = () => setHovering(false);
return (
<div onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
{hovering && <Tooltip id="info" />}
<svg
className="Icon-svg Icon--hoverable-svg"
height={this.props.height}
viewBox="0 0 16 16"
width="16"
>
<path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />
</svg>
</div>
);
};
Now we need to add the same functionality to our other two components, TrendChart and DailyChart. If it’s not broke, don’t fix it. Our hover logic for Info worked great so let’s use that same code again.
const TrendChart = () => {
const [hovering, setHovering] = useState(false);
const onMouseOver = () => setHovering(true);
const onMouseOut = () => setHovering(false);
return (
<div onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
{hovering && <Tooltip id="trend" />}
<Chart type="trend" />
</div>
);
};
You probably know the next step. We can do the same thing for our final DailyChart component.
const DailyChart = () => {
const [hovering, setHovering] = useState(false);
const onMouseOver = () => setHovering(true);
const onMouseOut = () => setHovering(false);
return (
<div onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
{hovering && <Tooltip id="daily" />}
<Chart type="daily" />
</div>
);
};
And with that, we’re all finished. As you probably noticed, it's not ver "DRY". We're repeating the exact same hover logic in every one of our components.
At this point, the problem should be pretty clear, we want to avoid duplicating our hover logic anytime a new component needs it. So what's the solution?
As you saw on the JavaScript Module, functions are "first-class objects". What that means is that just like objects/arrays/strings can be assigned to a variable, passed as an argument to a function, or returned from a function, so too can other functions.
If you have been practicing, then you have proably used a high-ordered funciton like the following.
[1, 2, 3].map((i) => i * 5);
So how can it help us?
# High-Ordered Function
- Is a function.
- Takes a callback function as an argument.
- Returns a new function.
- The function it returns can invoke the original callback function that was passed in.
# High-Ordered Component
- Is a component.
- Takes a component as an argument.
- Returns a new component.
- The component it returns can render the original component that was passed in.
const higherOrderComponent = (Component) => {
return (
<>
<Component />
</>
);
};
So now that we have the basic idea of what a higher-order component does, let’s start building ours out. If you’ll remember, the problem earlier was that we were duplicating all of our hover logic amongst all of the component that needed that functionality.
With that in mind, we want our higher-order component (which we’ll call withHover) to be able to encapsulate that hover logic in itself and then pass the hovering state to the component that it renders. That will allow us to prevent duplicating all the hover logic and instead, put it into a single location (withHover).
Ultimately, here’s the end goal. Whenever we want a component that is aware of it’s hovering state, we can pass the original component to our withHover higher-order component.
const InfoWithHover = withHover(Info);
const TrendChartWithHover = withHover(TrendChart);
const DailyChartWithHover = withHover(DailyChart);
Then, whenever any of the components that withHover returns are rendered, they’ll render the original component, passing it a hovering prop.
const Info = ({ hovering, height }) => {
return (
<>
{hovering && <Tooltip id="info" />}
<svg
className="Icon-svg Icon--hoverable-svg"
height={height}
viewBox="0 0 16 16"
width="16"
>
<path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />
</svg>
</>
);
};
Now the last thing we need to do is actually implement withHover. As we saw above, it needs to do three things.
- Take in a "Component" argument.
- Return a new component
- Render the "Component" argument passing it a "hovering" prop.
# Take in a Component argument
const withHover = (Component) => {};
# Return a new Component
const withHover = (Component) => {
return <Component />;
};
# Render the Component" argument passing it a hovering" prop
const withHover = (Component) => {
const [hovering, setHovering] = useState(false);
const onMouseOver = () => setHovering(true);
const onMouseOut = () => setHovering(false);
return (
<div onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
<Component hovering={hovering} />
</div>
);
};
At this point, we’ve seen the benefits of using Higher-Order Components to reuse component logic amongst various components without duplicating code.
← Forms React Context →