Technology · React
React Custom Hooks
Build reusable custom hooks to extract component logic and share stateful behavior.
TL;DR
- 01Extract stateful logic from components into reusable hook functions.
- 02Start every custom hook name with the word use.
- 03Call other hooks inside your hook to compose behavior.
Basic Structure
- Write a custom hook as a regular function that calls React hooks.
function useToggle(initialValue = false) { const [value, setValue] = useState(initialValue); const toggle = useCallback(() => { setValue(v => !v); }, []); return [value, toggle]; } - Return state, setters, and helper functions from your hook.
function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch(url) .then(r => r.json()) .then(setData) .finally(() => setLoading(false)); }, [url]); return { data, loading }; } - Keep each hook focused on a single concern.
Naming Rules
- Always start custom hook names with use, like useToggle.
// Good: clearly indicates it's a hook function useToggle() { } function useFetch(url) { } function useLocalStorage(key) { } // Bad: doesn't follow naming convention function toggle() { } function fetch() { } - Use camelCase after the use prefix for consistency.
- Naming triggers React ESLint rules and warnings.
- Pick names that describe what the hook does.
Common Patterns
- Build useToggle for managing boolean state.
function useToggle(initial = false) { const [value, setValue] = useState(initial); const toggle = () => setValue(!value); return [value, toggle]; } // Usage function Modal() { const [isOpen, toggleOpen] = useToggle(false); return ( <> <button onClick={toggleOpen}>Open</button> {isOpen && <div>Modal content</div>} </> ); } - Build usePrevious to track previous prop or state values.
function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); return ref.current; }
Rules of Hooks
- Only call hooks at the top level, never inside loops or conditions.
// Good: hook at top level function MyComponent() { const [count, setCount] = useState(0); useEffect(() => { /* ... */ }, []); return <div>{count}</div>; } // Bad: hook inside condition function MyComponent() { if (someCondition) { const [count, setCount] = useState(0); // WRONG! } } - Only call hooks from React components or custom hooks.
- Never call hooks conditionally since order must stay stable.
- ESLint plugin catches violations automatically.
Sharing State Across Components
- Each component gets its own state instance of a custom hook.
function useCounter() { const [count, setCount] = useState(0); return [count, () => setCount(count + 1)]; } function Component1() { const [count, increment] = useCounter(); return <button onClick={increment}>{count}</button>; } function Component2() { const [count, increment] = useCounter(); return <button onClick={increment}>{count}</button>; } // Each component has separate count state - Use Context API with custom hooks to share state.
function useAuth() { const context = useContext(AuthContext); if (!context) throw new Error("useAuth needs provider"); return context; }
Tip: Return values from custom hooks in the same format as built-in hooks — use arrays for positional access or objects for named access.
Warning: Each component that uses a custom hook gets its own isolated state instance — they don't automatically share state between them.