The Complete Guide to React Hooks
React Hooks were introduced in React 16.8, enabling you to use state and other React features inside functional components. They fundamentally changed how we write components.
Why Hooks?
Before Hooks, if you needed to manage state or handle side effects in a component, you had to use a class component:
class Counter extends React.Component {
state = { count: 0 }
increment = () => {
this.setState({ count: this.state.count + 1 })
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>+1</button>
</div>
)
}
}Rewritten with Hooks, the same component becomes far more concise:
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}useState
useState is the most fundamental Hook — it lets you add local state to a functional component.
const [state, setState] = useState(initialValue)Basic Usage
function LoginForm() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
console.log({ username, password })
}
return (
<form onSubmit={handleSubmit}>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Log in</button>
</form>
)
}Managing Multiple Fields with an Object
When you have many fields, group them into a single state object:
function ProfileForm() {
const [form, setForm] = useState({
name: '',
email: '',
bio: '',
})
const handleChange = (field) => (e) => {
setForm((prev) => ({ ...prev, [field]: e.target.value }))
}
return (
<form>
<input value={form.name} onChange={handleChange('name')} />
<input value={form.email} onChange={handleChange('email')} />
<textarea value={form.bio} onChange={handleChange('bio')} />
</form>
)
}Note: Unlike
setStatein class components, theuseStatesetter does not automatically merge objects — you need to spread...prevmanually.
useEffect
useEffect handles side effects such as data fetching, event subscriptions, and DOM manipulation.
useEffect(() => {
// side effect logic
return () => {
// cleanup function (optional)
}
}, [dependencies])Data Fetching
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
let cancelled = false
async function fetchUser() {
setLoading(true)
const res = await fetch(`/api/users/${userId}`)
const data = await res.json()
if (!cancelled) {
setUser(data)
setLoading(false)
}
}
fetchUser()
return () => {
cancelled = true
}
}, [userId])
if (loading) return <p>Loading...</p>
if (!user) return <p>User not found</p>
return <div>{user.name}</div>
}The Three Forms of the Dependency Array
| Form | Behavior |
|---|---|
| No second argument | Runs after every render |
[] empty array |
Runs once on mount only |
[a, b] |
Runs when a or b changes |
useContext
useContext lets you read a React Context value directly, without threading props through every layer of the tree.
Building a Theme Toggle
1. Define the Context:
// ThemeContext.js
import { createContext, useContext, useState } from 'react'
const ThemeContext = createContext()
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
const toggleTheme = () => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
return useContext(ThemeContext)
}2. Consume it in any component:
function Header() {
const { theme, toggleTheme } = useTheme()
return (
<header className={`header header--${theme}`}>
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
</header>
)
}useRef
useRef has two main uses: accessing DOM elements directly, and storing a mutable value that does not trigger a re-render when it changes.
function VideoPlayer() {
const videoRef = useRef(null)
return (
<div>
<video ref={videoRef} src="/video.mp4" />
<button onClick={() => videoRef.current.play()}>Play</button>
<button onClick={() => videoRef.current.pause()}>Pause</button>
</div>
)
}Custom Hooks
Custom Hooks are one of the most powerful features in React — extract reusable stateful logic into a standalone function and share it across multiple components.
useFetch
function useFetch(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let cancelled = false
async function fetchData() {
try {
setLoading(true)
const res = await fetch(url)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const json = await res.json()
if (!cancelled) setData(json)
} catch (err) {
if (!cancelled) setError(err.message)
} finally {
if (!cancelled) setLoading(false)
}
}
fetchData()
return () => { cancelled = true }
}, [url])
return { data, loading, error }
}Using it is clean and straightforward:
function PostList() {
const { data: posts, loading, error } = useFetch('/api/posts')
if (loading) return <p>Loading...</p>
if (error) return <p>Error: {error}</p>
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}Rules of Hooks
There are two rules you must always follow:
- Only call Hooks at the top level — never inside conditions, loops, or nested functions
- Only call Hooks from React functions — not from plain JavaScript functions
// ❌ Wrong: calling a Hook inside a condition
function Bad() {
if (someCondition) {
const [count, setCount] = useState(0) // not allowed!
}
}
// ✅ Correct: always call at the top level
function Good() {
const [count, setCount] = useState(0)
if (someCondition) {
return <div>{count}</div>
}
}Summary
| Hook | Purpose |
|---|---|
useState |
Manage local state |
useEffect |
Handle side effects (fetching, subscriptions, DOM) |
useContext |
Read Context values without prop drilling |
useRef |
Access DOM elements / store non-rendering mutable values |
useCallback |
Memoize function references to optimize child renders |
useMemo |
Memoize expensive computed values |
| Custom Hook | Extract and reuse stateful logic across components |
React Hooks significantly reduce code complexity and make logic reuse feel natural. Start with useState and useEffect, then work your way toward building your own custom Hooks.
Comments