Custom Hooks
Custom hooks are just functions that call other hooks -- that one rule unlocks the ability to extract fetch logic, timers, and subscriptions into clean, reusable units.
After adding StrictMode and React DevTools, the development environment is solid. Now there is a piece of logic worth extracting: fetching the pizza of the day.
The WET Rule
There is a common refactoring heuristic: DRY, or Don't Repeat Yourself. The version of this that applies specifically to hooks is WET -- Write Everything Twice. Not once, not three times. Twice.
The reasoning: the first time you write something, you are still figuring out the shape of the problem. Abstracting too early locks in an API before you understand what it needs to be. The second time you write it, the pattern is clear. The third time is when you extract it.
For the pizzeria app, usePizzaOfTheDay gets extracted not because the code is repeated but because it is genuinely a self-contained unit of behavior -- fetching a thing, managing loading state, returning the result. That combination is worth naming.
Writing the Custom Hook
Create src/usePizzaOfTheDay.js:
import { useState, useEffect } from "react";
const usePizzaOfTheDay = () => {
const [pizzaOfTheDay, setPizzaOfTheDay] = useState(null);
useEffect(() => {
fetch("/api/pizza-of-the-day")
.then((res) => res.json())
.then((data) => setPizzaOfTheDay(data));
}, []);
return pizzaOfTheDay;
};
export default usePizzaOfTheDay;A custom hook is any function that:
- Has a name starting with
use - Calls at least one React hook internally
That is the entire definition. There is no special API to implement, no class to extend, no registration step. The use prefix is a convention that tells both developers and the linter that this function follows the rules of hooks.
Using the Hook in a Component
import usePizzaOfTheDay from "./usePizzaOfTheDay";
const PizzaOfTheDay = () => {
const pizzaOfTheDay = usePizzaOfTheDay();
if (!pizzaOfTheDay) {
return <div>Loading pizza of the day...</div>;
}
return (
<div className="pizza-of-the-day">
<h2>Pizza of the Day</h2>
<img src={pizzaOfTheDay.image} alt={pizzaOfTheDay.name} />
<h3>{pizzaOfTheDay.name}</h3>
<p>{pizzaOfTheDay.description}</p>
</div>
);
};
export default PizzaOfTheDay;The component has no idea that a fetch is happening. It calls usePizzaOfTheDay(), gets back either null or a pizza object, and renders accordingly. The fetch logic, the loading state, and the effect are all inside the hook.
Each Instance Gets Its Own State
Custom hooks do not share state between calls. If two components both call usePizzaOfTheDay(), each component gets its own pizzaOfTheDay state variable and its own effect. They run independently. To share state between components, you need either a parent component holding the state, or context.
ExpandCustom hook: each component call gets its own isolated state and effect instance
When to Extract vs When to Leave It Inline
A custom hook is worth creating when:
- The combination of state + effect appears more than twice
- The behavior has a clear, nameable identity (
usePizzaOfTheDay,useWindowSize,useLocalStorage) - Tests need to cover the logic independently from the component
Leave it inline when it is used in one place and the component reads clearly without the extraction.
useDebugValue
There is one hook that exists purely for DevTools: useDebugValue. It has no effect on rendering. It attaches a label to your custom hook so it is identifiable in the React DevTools Components tab.
import { useState, useEffect, useDebugValue } from "react";
const usePizzaOfTheDay = () => {
const [pizzaOfTheDay, setPizzaOfTheDay] = useState(null);
useDebugValue(
pizzaOfTheDay ? `${pizzaOfTheDay.id}: ${pizzaOfTheDay.name}` : "loading"
);
useEffect(() => {
fetch("/api/pizza-of-the-day")
.then((res) => res.json())
.then((data) => setPizzaOfTheDay(data));
}, []);
return pizzaOfTheDay;
};In the Components tab, next to the usePizzaOfTheDay hook entry, DevTools will show the string you passed -- something like "42: Green Garden". Without it, you would just see the hook name. With it, you can tell at a glance what the hook is holding.
This is most useful when a codebase has many custom hooks and you need to distinguish them from each other in the panel. In a smaller app, it is optional.
The pizzeria app still needs a cart. The next post wires up form submission and builds the Cart component -- the first time user input actually changes what shows up in the UI.
The Essentials
- A custom hook is any function that starts with
useand calls at least one React hook. No special API, no registration. The naming convention is what enables linter rules and makes intent clear. - Each call to a custom hook gets its own state. Two components calling
usePizzaOfTheDay()each get independent state and effects. - Extract when the logic has a clear identity and appears more than twice. The WET rule: write it once, write it twice, extract on the third repetition.
useDebugValueadds a label to a custom hook in React DevTools. It has no effect on behavior and is development-only. Use it in hooks that would otherwise be hard to distinguish in large component trees.
Further Reading and Watching
- Reusing Logic with Custom Hooks (React docs) -- the official guide including how to test custom hooks and when to share state vs share logic
- useDebugValue (React docs) -- covers the optional formatter function for expensive transformations
- Custom React Hooks Explained (Web Dev Simplified) -- builds several real custom hooks from scratch showing the extraction process step by step
Keep reading