🧭 Introduction
Once you understand what makes a good custom hook, the next step is to build small, reusable utility hooks.
These hooks:
- Solve common problems
- Keep components clean
- Are used across almost every real-world React project
In this lesson, we’ll build two industry-standard utility hooks:
useToggleuseDebounce
And more importantly — understand why they’re designed this way.
🎯 What You’ll Learn in This Lesson
By the end of this lesson, you will understand:
- How to design simple utility hooks
- How to avoid unnecessary re-renders
- How to manage timing with hooks
- Real-world usage of
useToggleanduseDebounce
🔁 useToggle — Managing Boolean State
❓ The Problem
Many components need:
- Open / close
- Show / hide
- Enable / disable
Using useState repeatedly:
const [open, setOpen] = useState(false);
Logic gets duplicated everywhere.
🧩 Building useToggle
✅ Custom Hook
import { useState, useCallback } from "react";
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(v => !v);
}, []);
return [value, toggle];
}
🧠 Why This Design?
- Uses
useCallback→ stable function reference - Uses functional update → safe state change
- Simple API →
[value, toggle]
🔍 Using useToggle
function Modal() {
const [open, toggle] = useToggle(false);
return (
<>
<button onClick={toggle}>Toggle</button>
{open && <div className="modal">Modal</div>}
</>
);
}
Clean. Readable. Reusable.
🔁 useDebounce — Handling Rapid Changes
❓ The Problem
When users:
- Type in search box
- Resize window
- Scroll
Events fire too frequently.
🧠 What is Debouncing?
Debouncing:
Delays execution until the user stops triggering events
Used for:
- Search inputs
- API calls
- Validation
🧩 Building useDebounce
import { useEffect, useState } from "react";
function useDebounce(value, delay = 500) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
🧠 Why This Works
useEffectwatches value changes- Timer delays update
- Cleanup prevents memory leaks
- State updates only when user pauses
🔍 Using useDebounce
function Search() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 400);
useEffect(() => {
fetch(`/api/search?q=${debouncedQuery}`);
}, [debouncedQuery]);
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
);
}
👉 API calls happen only when typing stops.
⚠ Common Mistakes
❌ Mistake 1: Debouncing Inside Component
Leads to repeated logic and bugs.
❌ Mistake 2: Missing Cleanup
Timers must be cleared to avoid memory leaks.
❌ Mistake 3: Using Debounce for Everything
Not all problems need debouncing.
🧠 useToggle vs useDebounce (Mental Model)
| Hook | Purpose |
|---|---|
| useToggle | Boolean UI state |
| useDebounce | Rate-limiting updates |
Both:
- Improve performance
- Improve readability
🔍 Real-World Utility Hooks (Bonus)
Professional codebases often include:
usePrevioususeThrottleuseLocalStorageuseMediaQuery
All follow the same design principles.
🎯 Best Practices
✅ Keep utility hooks small
✅ Give clear, simple APIs
✅ Avoid hidden side effects
✅ Write hooks once, reuse everywhere
❓ FAQs — Utility Hooks
🔹 Should utility hooks be generic?
Yes — avoid business-specific logic.
🔹 Can hooks call other hooks?
Yes — composition is powerful.
🔹 Are utility hooks always worth it?
Only when logic repeats.
🔹 Should utility hooks be tested?
Yes — they’re easy to test.
🧠 Quick Recap
✔ useToggle simplifies boolean state
✔ useDebounce controls rapid changes
✔ Utility hooks improve performance
✔ Clean APIs matter
✔ Reusability is the goal
🎉 Conclusion
Utility hooks are small tools with big impact.
Once you build them:
- Your components become cleaner
- Your logic becomes reusable
- Your apps scale better
This lesson completes the utility hooks foundation ⚛️🧠
👉 Next Lesson
Lesson 11 — API Custom Hooks (useFetch / useAsync)