What are Custom Hooks?
Overview
When you build UI components, you can share visual logic (like a `<Button>`). But what if you want to share *stateful behavior*? For example, detecting if a user is online, tracking window dimensions, or handling form state. Custom Hooks allow you to extract `useState` and `useEffect` logic into completely reusable JavaScript functions. They are the ultimate tool for keeping components clean and DRY.
Syntax
A custom hook is just a normal JavaScript function that calls other hooks. By convention, it must start with `use` so React knows to enforce the Rules of Hooks inside it.
// 1. MUST start with the word "use"
import { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// 2. Return the stateful data
return width;
}The component is drastically simplified. All the messy event listener and cleanup logic is hidden away inside the custom hook.
// Now any component can track window width in one line of code!
function ResponsiveLayout() {
const width = useWindowWidth();
if (width < 768) {
return <MobileMenu />;
}
return <DesktopSidebar />;
}Common Pitfalls
- Naming a custom hook without the `use` prefix (e.g., `getWindowWidth`). The React linter will not check it for hook violations, leading to insidious bugs.
- Putting JSX inside a custom hook. Hooks should return data (arrays, objects, primitives), not UI. If it returns UI, it's a Component, not a Hook.
Interview Tips
- Be clear that Custom Hooks share *logic*, not state itself. If two components use `useWindowWidth`, they each get their own entirely independent `width` state variable.
Real-World Example
A hook that synchronizes state with the browser's `localStorage`.
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue]; // Returns an array mimicking useState
}
// Usage (Works exactly like useState, but persists on refresh!)
// const [theme, setTheme] = useLocalStorage('theme', 'dark');