Custom Hooks
Custom Hooks are the crown jewel of React’s composition model. They allow you to extract component logic into reusable functions. If you find yourself copying and pasting useEffect code between components, you likely need a Custom Hook.
The Rules of Custom Hooks
- Naming: Must start with
use(e.g.,useWindowSize). This tells React to check for violations of the Rules of Hooks. - Usage: Can call other hooks (
useState,useEffect, etc.). - Independence: Two components using the same hook do NOT share state. Each gets its own isolated state instance.
Pattern 1: Logic Extraction (Before vs After)
Imagine you have a component that tracks the window size.
Before: Bloated Component
function App() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return <div>Width: {windowSize.width}px</div>;
}
After: Clean Component with useWindowSize
We extract the state and effect into a separate function.
// The Custom Hook
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}
// The Component (Now Trivial)
function App() {
const size = useWindowSize();
return <div>Width: {size.width}px</div>;
}
Pattern 2: Synchronizing with Browser APIs
A classic use case is syncing state with browser APIs like localStorage or window.
useLocalStorage
function useLocalStorage(key, initialValue) {
// 1. Initialize state from local storage or default
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
// 2. Return a wrapped version of useState's setter function
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
}
Interactive: Hook Composition Demo
See how multiple small hooks combine to power a complex UI. We will compose a simulated useMousePosition hook and a useWindowSize hook to create a responsive, interactive tracking component.
Active Hooks
Visualizing Logic Extraction
This diagram shows how we move logic from the Component into a Custom Hook.
Key Takeaways
- DRY (Don’t Repeat Yourself): If you copy-paste logic, make a hook.
- Encapsulation: Hide complex implementation details (like
AbortController) inside the hook. - Composition: Hooks can use other hooks, allowing you to build complex logic from simple blocks.