Hooks Pattern
Use functions to reuse stateful logic among multiple components throughout the app
Overview
React Hooks are functions special types of functions that you can use in order to:
- Add state to a functional component
- Reuse stateful logic among multiple components throughout the app.
- Manage a component's lifecycle
Besides built-in hooks, such as useState
, useEffect
, and useReducer
, we can create custom hooks to easiliy share stateful logic across multiple components within the application.
For example, if we wanted to see if a certain component is currently being hovered, we can create and use a useHover
hook.
We could just add this logic to the Listing
component itself. However, in order to make the hover logic reusable throughout the application, it makes more sense to create its own hook for it.
Now that it's a hook, we can reuse the same logic throughout multiple components in our application. For example, if we also wnated to add the hover logic to the Image
and Button
component, we can simply use the hook within these functional components.
Implementation
React knows a hook is a hook when the name starts with use
. Since this hook keeps track of the hovering state, it makes sense to name it useHover
.
Within this hook, we have to keep track of the hovering state by using three build-in hooks:
useState
, which keeps track of whether the component is currently being hovered.useRef
, which creates a ref to the component that we're tracking.useEffect
, which gets executed as soon as theref
has a value. In here, we can add event listeners to the component to keep track of the hovering state.
export function useHover() {
const [isHovering, setIsHovering] = React.useState(false);
const ref = React.useRef(null);
const handleMouseOver = () => setIsHovering(true);
const handleMouseOut = () => setIsHovering(false);
React.useEffect(() => {
const node = ref.current;
if (node) {
node.addEventListener("mouseover", handleMouseOver);
node.addEventListener("mouseout", handleMouseOut);
return () => {
node.removeEventListener("mouseover", handleMouseOver);
node.removeEventListener("mouseout", handleMouseOut);
};
}
}, [ref.current]);
return [ref, isHovering];
}
We can use this hook in any component that cares about the hovering state.
import { useHover } from "../hooks/useHover";
export function Listing() {
const [ref, isHovering] = useHover();
React.useEffect(() => {
if (isHovering) {
// Add logic here
}
}, [isHovering]);
return (
<div ref={ref}>
<ListingCard />
</div>
);
}
Tradeoffs
Simplifies components: Hooks make it easy to add state to functional components, rather than (usually more complex) class components.
Reusing stateful logic: Hooks allow you to reuse stateful logic among multiple components across the application, which reduces the chances of errors, and allows for composition with plain functions.
Sharing non-visual logic: Hooks make it easy to share non-visual logic, without having to use patterns like HOC or Render Props
Good alternative to older React design patterns: The hooks pattern is a good alternative to an older React design pattern, which is mainly used with Class components, namely the Presentational/Container pattern.
With Hooks, we no longer have to wrap Presentational components in Container components to pass data. Instead, we can directly use the Hook within the presentational component.
Rules of Hooks: Hooks require certain rules to be followed. without a linter plugin, it is difficult to know which rule has been broken, and you can accidentally end up using the wrong built-in hook.
Exercise
Challenge
The code below is using the Container/Presentational pattern to display the listings. Refactor this code so that it uses a hook instead of a container component.