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: