React Hooks Cheat Sheet
Complete reference for React Hooks with practical examples. From basic state management to advanced custom hooks.
useState
The most commonly used hook for adding state to functional components.
| Pattern | Example | Description |
|---|---|---|
| Basic Usage | const [count, setCount] = useState(0); |
Initialize state with a primitive value |
| Update State | setCount(count + 1); |
Direct state update |
| Functional Update | setCount(prev => prev + 1); |
Update based on previous state (safer for async) |
| Object State | const [user, setUser] = useState({ name: '', age: 0 }); |
Initialize with an object |
| Update Object | setUser(prev => ({ ...prev, name: 'John' })); |
Merge object updates (doesn't auto-merge) |
| Array State | const [items, setItems] = useState([]); |
Initialize with an array |
| Add to Array | setItems(prev => [...prev, newItem]); |
Add item to array immutably |
| Remove from Array | setItems(prev => prev.filter(item => item.id !== id)); |
Remove item from array |
| Lazy Initialization | const [state, setState] = useState(() => expensiveComputation()); |
Initialize state with a function (runs only once) |
useEffect
Perform side effects in functional components (data fetching, subscriptions, DOM manipulation).
| Pattern | Example | Description |
|---|---|---|
| Basic Effect | useEffect(() => { document.title = title; }); |
Runs after every render |
| Run Once (Mount) | useEffect(() => { fetchData(); }, []); |
Empty dependency array = runs once on mount |
| With Dependencies | useEffect(() => { fetchUser(userId); }, [userId]); |
Runs when dependencies change |
| Cleanup Function | useEffect(() => { const timer = setInterval(...); return () => clearInterval(timer); }, []); |
Return cleanup function to prevent memory leaks |
| Event Listeners | useEffect(() => { window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); |
Add event listener and clean up on unmount |
| Async Effect | useEffect(() => { const fetchData = async () => { const result = await api.get('/data'); setData(result); }; fetchData(); }, []); |
Use async function inside effect (can't make effect itself async) |
| Conditional Effect | useEffect(() => { if (isReady) { doSomething(); } }, [isReady]); |
Run effect conditionally |
| Multiple Effects | useEffect(() => { /* effect 1 */ }, [dep1]); useEffect(() => { /* effect 2 */ }, [dep2]); |
Separate concerns into multiple effects |
useContext
Access React context without wrapping components in Consumer.
| Pattern | Example | Description |
|---|---|---|
| Create Context | const ThemeContext = React.createContext('light'); |
Create a context with default value |
| Provide Context | <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider> |
Provide context value to descendants |
| Consume Context | const theme = useContext(ThemeContext); |
Access context value in any child component |
| With State | const [theme, setTheme] = useState('light'); return <ThemeContext.Provider value={{ theme, setTheme }}> |
Provide stateful value and setter |
| Multiple Contexts | const theme = useContext(ThemeContext); const user = useContext(UserContext); |
Use multiple contexts in one component |
| Nested Providers | <ThemeProvider><UserProvider><App /></UserProvider></ThemeProvider> |
Nest multiple context providers |
| Custom Hook | const useTheme = () => { const context = useContext(ThemeContext); if (!context) throw new Error('useTheme must be used within ThemeProvider'); return context; }; |
Create custom hook for context with error checking |
useReducer
Alternative to useState for complex state logic. Similar to Redux reducers.
| Pattern | Example | Description |
|---|---|---|
| Basic Reducer | const reducer = (state, action) => { switch(action.type) { case 'increment': return { count: state.count + 1 }; default: return state; } }; |
Define reducer function |
| Initialize | const [state, dispatch] = useReducer(reducer, { count: 0 }); |
Initialize reducer with initial state |
| Dispatch Action | dispatch({ type: 'increment' }); |
Dispatch action to update state |
| Action with Payload | dispatch({ type: 'add', payload: 5 }); |
Include data in action |
| Lazy Initialization | const init = (initialCount) => ({ count: initialCount }); const [state, dispatch] = useReducer(reducer, 0, init); |
Initialize state lazily with function |
| Complex State | const [state, dispatch] = useReducer(reducer, { users: [], loading: false, error: null }); |
Manage multiple related state values |
| vs useState | // Use useReducer when: multiple sub-values, complex logic, next state depends on previous |
When to choose useReducer over useState |
useCallback
Memoize callback functions to prevent unnecessary re-renders of child components.
| Pattern | Example | Description |
|---|---|---|
| Basic Usage | const handleClick = useCallback(() => { doSomething(); }, []); |
Memoize callback with no dependencies |
| With Dependencies | const handleClick = useCallback(() => { doSomething(value); }, [value]); |
Recreate callback when dependencies change |
| With Child Component | const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); return <Child onClick={memoizedCallback} />; |
Prevent child re-render when callback hasn't changed |
| Event Handler | const handleSubmit = useCallback((e) => { e.preventDefault(); submitForm(data); }, [data]); |
Memoize event handler |
| With Multiple Deps | const calculate = useCallback(() => { return a * b + c; }, [a, b, c]); |
Multiple dependencies in array |
| Inline Arrow Function | const handleClick = useCallback((id) => { deleteItem(id); }, [deleteItem]); |
Memoize function that takes parameters |
| When to Use | // Use when passing callbacks to optimized child components wrapped in React.memo() |
Optimize performance by preventing child re-renders |
useMemo
Memoize expensive computations to avoid recalculating on every render.
| Pattern | Example | Description |
|---|---|---|
| Basic Usage | const expensiveValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); |
Memoize computed value |
| Array Filtering | const filteredList = useMemo(() => items.filter(item => item.active), [items]); |
Memoize filtered array |
| Array Sorting | const sortedList = useMemo(() => [...items].sort((a, b) => a.name.localeCompare(b.name)), [items]); |
Memoize sorted array |
| Object Creation | const memoizedValue = useMemo(() => ({ name, age }), [name, age]); |
Memoize object to maintain referential equality |
| Expensive Calculation | const fibonacci = useMemo(() => calculateFibonacci(n), [n]); |
Cache result of expensive computation |
| Derived State | const total = useMemo(() => cart.reduce((sum, item) => sum + item.price, 0), [cart]); |
Calculate derived state from props/state |
| vs useCallback | // useMemo returns memoized VALUE, useCallback returns memoized FUNCTION |
useMemo for values, useCallback for functions |
| When to Use | // Use for computationally expensive operations, not simple calculations |
Don't overuse - adds overhead for simple operations |
useRef
Access DOM elements directly or persist mutable values across renders without causing re-renders.
| Pattern | Example | Description |
|---|---|---|
| Create Ref | const inputRef = useRef(null); |
Create ref with initial value |
| Attach to DOM | <input ref={inputRef} /> |
Attach ref to DOM element |
| Access DOM Element | inputRef.current.focus(); |
Access DOM element via .current |
| Mutable Value | const countRef = useRef(0); countRef.current += 1; |
Store mutable value that doesn't trigger re-render |
| Previous Value | const prevValueRef = useRef(); useEffect(() => { prevValueRef.current = value; }); |
Store previous prop or state value |
| Store Interval/Timer | const intervalRef = useRef(); intervalRef.current = setInterval(...); return () => clearInterval(intervalRef.current); |
Store timer ID for cleanup |
| Forward Ref | const Input = React.forwardRef((props, ref) => <input ref={ref} {...props} />); |
Pass ref to child component |
| useImperativeHandle | useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus() })); |
Customize ref value exposed to parent |
| Callback Ref | const setRef = useCallback(node => { if (node) { node.focus(); } }, []); |
Use callback instead of ref object for more control |
useLayoutEffect
Similar to useEffect, but fires synchronously after all DOM mutations. Use for DOM measurements.
| Pattern | Example | Description |
|---|---|---|
| Basic Usage | useLayoutEffect(() => { // DOM manipulation }, []); |
Runs synchronously after DOM updates |
| DOM Measurements | useLayoutEffect(() => { const rect = divRef.current.getBoundingClientRect(); setHeight(rect.height); }, []); |
Measure DOM before browser paint |
| vs useEffect | // useLayoutEffect: fires before paint (synchronous), useEffect: fires after paint (asynchronous) |
useLayoutEffect blocks visual updates |
| When to Use | // Use when you need to read layout from DOM and synchronously re-render |
Prevent flickering from layout shifts |
| Scroll Position | useLayoutEffect(() => { window.scrollTo(0, savedPosition); }, []); |
Restore scroll position before paint |
| Tooltip Position | useLayoutEffect(() => { const pos = calculateTooltipPosition(targetRef.current); setPosition(pos); }, [targetRef]); |
Position tooltips/popovers without flicker |
Custom Hooks
Build your own hooks to extract and reuse stateful logic across components.
| Pattern | Example | Description |
|---|---|---|
| Basic Custom Hook | function useCounter(initialValue = 0) { const [count, setCount] = useState(initialValue); const increment = () => setCount(c => c + 1); return { count, increment }; } |
Extract stateful logic into reusable hook |
| Data Fetching Hook | function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch(url).then(r => r.json()).then(d => { setData(d); setLoading(false); }); }, [url]); return { data, loading }; } |
Reusable data fetching logic |
| Local Storage Hook | 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]; } |
Sync state with localStorage |
| Window Size Hook | function useWindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const handleResize = () => setSize({ width: window.innerWidth, height: window.innerHeight }); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return size; } |
Track window dimensions |
| Debounce Hook | function useDebounce(value, delay) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebouncedValue(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debouncedValue; } |
Debounce value changes |
| Form Hook | function useForm(initialValues) { const [values, setValues] = useState(initialValues); const handleChange = (e) => { setValues({ ...values, [e.target.name]: e.target.value }); }; const reset = () => setValues(initialValues); return { values, handleChange, reset }; } |
Handle form state |
| Previous Value Hook | function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; } |
Get previous value of prop or state |
| Toggle Hook | function useToggle(initialValue = false) { const [value, setValue] = useState(initialValue); const toggle = () => setValue(v => !v); return [value, toggle]; } |
Simple boolean toggle |
| Interval Hook | function useInterval(callback, delay) { const savedCallback = useRef(); useEffect(() => { savedCallback.current = callback; }); useEffect(() => { if (delay !== null) { const id = setInterval(() => savedCallback.current(), delay); return () => clearInterval(id); } }, [delay]); } |
Declarative setInterval |
Rules of Hooks
Critical rules you must follow when using hooks.
| Rule | Example | Why It Matters |
|---|---|---|
| Only Call at Top Level | // ✅ const [count, setCount] = useState(0); // ❌ if (condition) { const [count, setCount] = useState(0); } |
Don't call hooks inside loops, conditions, or nested functions |
| Only Call from React Functions | // ✅ Function component or custom hook // ❌ Regular JavaScript function |
Only call hooks from React function components or custom hooks |
| Custom Hook Naming | // ✅ function useCustomHook() {} // ❌ function customHook() {} |
Custom hooks must start with "use" prefix |
| Consistent Order | // React relies on hook call order to maintain state between renders |
Hooks must be called in the same order every render |
| ESLint Plugin | // Use eslint-plugin-react-hooks to enforce rules automatically |
Install the ESLint plugin to catch violations |
Common Patterns & Tips
Best practices and solutions to common problems when working with hooks.
| Pattern | Example | Description |
|---|---|---|
| Cleanup Pattern | useEffect(() => { const subscription = api.subscribe(); return () => subscription.unsubscribe(); }, []); |
Always cleanup subscriptions, timers, listeners |
| Stale Closure Fix | // Use functional update: setCount(c => c + 1) instead of setCount(count + 1) |
Avoid stale values in closures |
| Abort Fetch | useEffect(() => { const controller = new AbortController(); fetch(url, { signal: controller.signal }).then(setData); return () => controller.abort(); }, [url]); |
Cancel fetch requests on unmount |
| Conditional Effects | useEffect(() => { if (!isReady) return; fetchData(); }, [isReady]); |
Guard effects with early returns |
| Deep Comparison | // Use a library like use-deep-compare-effect for object/array dependencies |
Standard useEffect uses shallow comparison |
| Performance Tip | // Don't optimize prematurely. Profile first, then add useMemo/useCallback if needed |
Memoization adds overhead, use judiciously |
| Multiple State Updates | // Use useReducer when you have multiple setState calls that depend on each other |
Simplify complex state logic |
| Derived State | // Calculate derived values during render, don't store in state: const total = items.reduce((sum, item) => sum + item.price, 0); |
Avoid syncing state unnecessarily |
| Error Boundaries | // Hooks don't have error boundary equivalent yet, wrap components with class-based error boundaries |
Handle errors in components using hooks |
| Testing Hooks | // Use @testing-library/react-hooks to test custom hooks in isolation |
Test hooks without wrapping in components |
| Async State Updates | // State updates are batched in React 18+, even outside event handlers |
Multiple setState calls batch automatically |
| Refs vs State | // Use ref when you need to store a value that shouldn't trigger re-render |
Refs for mutable values, state for UI updates |
More Developer Resources
Check out our other cheat sheets and tools: