Custom Hooks
MedCustom hooks extract reusable stateful logic into functions that start with "use". They allow you to share behavior between components without changing the component hierarchy. Each component calling a custom hook gets its own independent copy of the state.
Interactive Visualization
Key Points
- Must start with "use" to follow the rules of hooks
- Can call other hooks inside them including useState, useEffect, and other custom hooks
- Each component using the hook gets independent state
- Replace render props and HOCs for sharing stateful logic
- Should do one thing well and be composable with other hooks
- Test by testing the component that uses them or with renderHook from testing library
Code Examples
useLocalStorage Hook
import { useState, useEffect } from 'react' function useLocalStorage<T>( key: string, initialValue: T ): [T, (value: T | ((prev: T) => T)) => void] { const [stored, setStored] = useState<T>(() => { try { const item = localStorage.getItem(key) return item ? JSON.parse(item) as T : initialValue } catch { return initialValue } }) useEffect(() => { try { localStorage.setItem(key, JSON.stringify(stored)) } catch { // Storage full or unavailable } }, [key, stored]) return [stored, setStored] } function Settings() { const [theme, setTheme] = useLocalStorage('theme', 'dark') const [fontSize, setFontSize] = useLocalStorage('fontSize', 16) return ( <div> <select value={theme} onChange={(e) => setTheme(e.target.value)}> <option value="dark">Dark</option> <option value="light">Light</option> </select> <input type="range" value={fontSize} onChange={(e) => setFontSize(Number(e.target.value))} /> </div> ) }
The hook encapsulates localStorage read/write logic that any component can reuse, each getting its own independent value
useDebounce Hook
import { useState, useEffect } from 'react' function useDebounce<T>(value: T, delay: number): T { const [debounced, setDebounced] = useState<T>(value) useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay) return () => clearTimeout(timer) }, [value, delay]) return debounced } function SearchBar() { const [input, setInput] = useState('') const debouncedQuery = useDebounce(input, 300) useEffect(() => { if (debouncedQuery) { // Fetch results only after user stops typing fetch(`/api/search?q=${debouncedQuery}`) } }, [debouncedQuery]) return ( <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="Search..." /> ) }
useDebounce delays updating the returned value until the input has been stable for the specified delay, reducing API calls
useMediaQuery Hook
import { useState, useEffect } from 'react' function useMediaQuery(query: string): boolean { const [matches, setMatches] = useState(() => typeof window !== 'undefined' ? window.matchMedia(query).matches : false ) useEffect(() => { const mql = window.matchMedia(query) const handler = (e: MediaQueryListEvent) => setMatches(e.matches) mql.addEventListener('change', handler) setMatches(mql.matches) return () => mql.removeEventListener('change', handler) }, [query]) return matches } function ResponsiveNav() { const isMobile = useMediaQuery('(max-width: 768px)') return isMobile ? <HamburgerMenu /> : <DesktopNav /> }
The hook subscribes to a CSS media query, returning a reactive boolean that updates when the viewport crosses the breakpoint
Common Mistakes
- Not starting the function name with "use" which disables lint rules for hook ordering
- Assuming custom hooks share state between components that call them
- Creating custom hooks that do too many things instead of composable single-purpose hooks
Interview Tips
- Explain that custom hooks share logic but not state between components
- Compare custom hooks to HOCs and render props as patterns for code reuse
- Be ready to write a custom hook from scratch such as useFetch or useDebounce
Related Concepts
useState & State Updates
Adding and updating local component state
useEffect & Side Effects
Synchronizing components with external systems
useRef & Mutable References
Persistent mutable values without re-renders
Render Props
Sharing logic via functions that return JSX
Higher-Order Components
Functions that enhance components with extra behavior