Complete guide to all React hooks with practical examples, use cases, and best practices
Hooks are functions that let you "hook into" React state and lifecycle features from function components. Introduced in React 16.8, hooks provide a more direct API to the React concepts you already know: props, state, context, refs, and lifecycle.
Hooks let you use state and other React features without writing a class. They make it easier to reuse stateful logic between components and organize component code by related functionality.
eslint-plugin-react-hooks to enforce these rules
The useState hook lets you add state to functional components. It returns a pair:
the current state value and a function to update it.
import React, { useState } from 'react'; function Counter() { // Declare a state variable "count" with initial value 0 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
function Counter() { const [count, setCount] = useState(0); // Functional update - recommended when new state depends on previous state const increment = () => { setCount(prevCount => prevCount + 1); }; // Multiple updates in same event will batch correctly const incrementByFive = () => { setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); }; return <button onClick={increment}>Count: {count}</button>; }
The useEffect hook lets you perform side effects in function components. It serves the same
purpose as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
import { useState, useEffect } from 'react'; function DocumentTitle() { const [count, setCount] = useState(0); // Runs after every render (no dependency array) useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <button onClick={() => setCount(count + 1)}> Click me </button> ); }
function ChatRoom({ roomId }) { useEffect(() => { // Setup: Subscribe to chat room const connection = createConnection(roomId); connection.connect(); // Cleanup function: Unsubscribe when component unmounts or roomId changes return () => { connection.disconnect(); }; }, [roomId]); // Only re-run if roomId changes return <div>Connected to {roomId}</div>; }
// Runs after EVERY render useEffect(() => { console.log('Runs on every render'); }); // Runs only ONCE after initial render (like componentDidMount) useEffect(() => { console.log('Runs only once'); }, []); // Runs when count or name changes useEffect(() => { console.log('Runs when count or name changes'); }, [count, name]);
The useContext hook accepts a context object and returns the current context value.
It's a cleaner way to consume context compared to Context.Consumer.
import { createContext, useContext, useState } from 'react'; // Create context const ThemeContext = createContext(); // Provider component function App() { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={{ theme, setTheme }}> <Toolbar /> </ThemeContext.Provider> ); } // Consumer component using useContext function ThemedButton() { const { theme, setTheme } = useContext(ThemeContext); return ( <button style={{ background: theme === 'dark' ? '#333' : '#fff' }} onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} > Toggle Theme </button> ); }
The useReducer hook is an alternative to useState for managing complex state logic.
It's preferable when you have multiple sub-values or when the next state depends on the previous one.
import { useReducer } from 'react'; // Reducer function function counterReducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return { count: 0 }; default: throw new Error('Unknown action'); } } function Counter() { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> <button onClick={() => dispatch({ type: 'reset' })}>Reset</button> </div> ); }
The useRef hook returns a mutable ref object whose .current property is initialized to the
passed argument. The returned object will persist for the full lifetime of the component.
import { useRef, useEffect } from 'react'; function TextInputWithFocusButton() { const inputRef = useRef(null); const focusInput = () => { // Access DOM element directly inputRef.current.focus(); }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={focusInput}>Focus Input</button> </div> ); }
function Timer() { const [count, setCount] = useState(0); const intervalRef = useRef(); useEffect(() => { // Store interval ID in ref intervalRef.current = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(intervalRef.current); }, []); const stopTimer = () => { clearInterval(intervalRef.current); }; return ( <div> <p>Count: {count}</p> <button onClick={stopTimer}>Stop</button> </div> ); }
The useMemo hook memoizes expensive computations and only recomputes when dependencies change.
It's an optimization tool to avoid expensive calculations on every render.
import { useMemo, useState } from 'react'; function expensiveCalculation(num) { console.log('Calculating...'); for (let i = 0; i < 1000000000; i++) {} // Simulate expensive operation return num * 2; } function Counter() { const [count, setCount] = useState(0); const [input, setInput] = useState(''); // Only recalculates when 'count' changes, not when 'input' changes const doubled = useMemo(() => { return expensiveCalculation(count); }, [count]); return ( <div> <input value={input} onChange={e => setInput(e.target.value)} /> <button onClick={() => setCount(count + 1)}>Increment</button> <p>Doubled: {doubled}</p> </div> ); }
The useCallback hook returns a memoized callback function. It's useful when passing callbacks
to optimized child components that rely on reference equality to prevent unnecessary renders.
import { useState, useCallback, memo } from 'react'; // Child component wrapped in React.memo const Button = memo(({ onClick, children }) => { console.log('Button rendered'); return <button onClick={onClick}>{children}</button>; }); function Parent() { const [count, setCount] = useState(0); const [otherState, setOtherState] = useState(0); // Callback only recreated when count changes const increment = useCallback(() => { setCount(count + 1); }, [count]); return ( <div> <p>Count: {count}</p> <Button onClick={increment}>Increment</Button> <button onClick={() => setOtherState(otherState + 1)}> Other State </button> </div> ); }
Custom hooks let you extract component logic into reusable functions. A custom hook is a JavaScript function whose name starts with "use" and that may call other hooks.
import { useState, useEffect } from 'react'; function useLocalStorage(key, initialValue) { // Get from local storage or use initial value const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.log(error); return initialValue; } }); // Update local storage when value changes const setValue = (value) => { try { setStoredValue(value); window.localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.log(error); } }; return [storedValue, setValue]; } // Usage function App() { const [name, setName] = useLocalStorage('name', 'Guest'); return ( <input value={name} onChange={e => setName(e.target.value)} /> ); }
function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const response = await fetch(url); const json = await response.json(); setData(json); setError(null); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchData(); }, [url]); return { data, loading, error }; } // Usage function UserList() { const { data, loading, error } = useFetch('https://api.example.com/users'); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>; }
useLocalStorage - Sync state with localStorageuseFetch - Fetch data from APIsuseDebounce - Debounce state valuesuseWindowSize - Track window dimensionsuseOnClickOutside - Detect clicks outside element| Hook | Purpose | Returns | When to Use |
|---|---|---|---|
useState |
Manage component state | [state, setState] | Simple state management |
useEffect |
Handle side effects | Cleanup function (optional) | Data fetching, subscriptions, DOM updates |
useContext |
Access context value | Context value | Global state, theme, auth |
useReducer |
Manage complex state | [state, dispatch] | Multiple sub-values, complex logic |
useRef |
Persist mutable values | Ref object | DOM access, storing values without re-render |
useMemo |
Memoize expensive calculations | Memoized value | Performance optimization |
useCallback |
Memoize callback functions | Memoized function | Prevent child re-renders |