React Hooks Tutorial 2025 — Master React Hooks easily with this complete beginner-friendly guide! Learn useState, useEffect, useContext, useReducer, and custom hooks with 15+ real code examples, best practices, and common mistakes explained.

React Hooks Tutorial 2025: Complete Beginner to Advanced Guide
React Hooks changed how developers write React components back in 2016 version 16.8. In 2025, they’re the standard way to build React applications.
I’ve been building React apps for six years now. Worked on maybe 40+ projects using Hooks extensively. Seen beginners struggle with the same concepts repeatedly. Also watched experienced developers write inefficient Hook patterns.
This React Hooks tutorial covers everything from absolute basics to advanced patterns. useState fundamentals. useEffect mastery. Custom Hooks creation. Real working examples you can copy immediately.
No confusing jargon without explanation. Just straightforward examples showing how React Hooks actually work in real applications.
Whether you’re migrating from class components or learning React fresh in 2025, this guide gets you writing modern React confidently.
Ready to master Hooks? Let’s start from the beginning.
What Are React Hooks?
React Hooks are functions letting you use state and other React features in functional components without writing classes.
Before Hooks, stateful logic required class components. Managing state meant writing this.setState and binding methods. Complex and verbose.
Hooks simplified everything dramatically:
Old Way – Class Component
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return <div>{this.state.count}</div>;
}
}
New Way – Hooks
function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
Same functionality. Way less code. Much clearer logic.
React Hooks let functional components handle state, side effects, context, and more. No classes needed anymore.
Why Use React Hooks in 2025?
Modern React development centers around Hooks completely now.
Key benefits:
Simpler Code Functional components with Hooks use 30-40% less code than equivalent class components. Less boilerplate. Clearer logic flow.
Better Code Reuse Custom Hooks let you extract stateful logic into reusable functions. Share complex logic across components easily.
Easier Testing Functional components test simpler than classes. No instance methods to mock. Pure functions easier to predict.
Performance Optimization Hooks like useMemo and useCallback optimize rendering efficiently. Prevent unnecessary re-renders easily.Industry Standard Every major React project in 2025 uses Hooks. React team recommends Hooks for all new code. Learning Hooks is learning modern React.
React useState Hook Explained
useState manages component state in functional components.
Basic syntax:
const [state, setState] = useState(initialValue);
Real Example – Counter Component:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<button onClick={() => setCount(0)}>
Reset
</button>
</div>
);
}
How it works:
- useState(0) initializes state to 0
- Returns array: current state value and update function
- count holds current value
- setCount updates the value
- Component re-renders when state changes
Multiple State Variables in React
In React, you can use multiple useState hooks to manage different pieces of state independently within the same component.
Example:
function UserForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);
return (
<form>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
/>
</form>
);
}
Common mistake: Never modify state directly. Always use the setter function.
// ❌ Wrong
count = count + 1;
// ✅ Correct
setCount(count + 1);
React useEffect Hook Tutorial
useEffect handles side effects in functional components. Data fetching, subscriptions, DOM manipulation — all go inside useEffect.
Basic Syntax:
useEffect(() => {
// Side effect code here
return () => {
// Cleanup code (optional)
};
}, [dependencies]);
Example – Fetching Data:
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(`https://api.example.com/users/${userId}`)
.then(response => response.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]); // Re-run when userId changes
if (loading) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
Dependency Array Rules:
// Runs once on mount (like componentDidMount)
useEffect(() => {
console.log('Component mounted');
}, []);
// Runs on every render (usually avoid this)
useEffect(() => {
console.log('Every render');
});
// Runs when specific values change
useEffect(() => {
console.log('userId changed');
}, [userId]);
Cleanup Function Example:
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// Cleanup: clear interval when component unmounts
return () => clearInterval(interval);
}, []);
return <div>Seconds: {seconds}</div>;
}
Common Mistakes:
// ❌ Missing dependencies
useEffect(() => {
console.log(count); // count not in array
}, []); // Warning: missing dependency
// ✅ Include all dependencies
useEffect(() => {
console.log(count);
}, [count]);
React useContext Hook Guide
useContext allows functional components to access Context values directly — without the need to wrap components in a Consumer.
Creating and Using Context:
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
function Toolbar() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div style={{ background: theme === 'light' ? '#fff' : '#333' }}>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</div>
);
}
Why Use Context in React
Why use Context: To avoid prop drilling through multiple component levels. It allows sharing global data like themes, user authentication, or language preferences across the app.
Real-world Example – Authentication:
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (userData) => setUser(userData);
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
function Profile() {
const { user, logout } = useContext(AuthContext);
if (!user) return <div>Please log in</div>;
return (
<div>
<h1>Welcome {user.name}</h1>
<button onClick={logout}>Logout</button>
</div>
);
}

React useReducer Hook Explained
useReducer manages complex state logic. It’s an alternative to useState when you have multiple sub-values or intricate state transitions.
Syntax:
const [state, dispatch] = useReducer(reducer, initialState);
Example – Todo List:
import { useReducer, useState } from 'react';
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text, done: false }];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
);
case 'DELETE_TODO':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, []);
const [input, setInput] = useState('');
const addTodo = () => {
dispatch({ type: 'ADD_TODO', text: input });
setInput('');
};
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={addTodo}>Add</button>
{todos.map(todo => (
<div key={todo.id}>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}>
Toggle
</button>
<button onClick={() => dispatch({ type: 'DELETE_TODO', id: todo.id })}>
Delete
</button>
</div>
))}
</div>
);
}
When to Use useReducer
- Complex state logic with multiple sub-values
- Next state depends on previous state
- State transitions need to be predictable
- Want to optimize performance
Custom React Hooks Tutorial
Custom Hooks extract reusable stateful logic. They help you build cleaner, modular React code. Follow these simple rules when creating them:
- The name must start with
use - You can call other Hooks inside
- They can return anything useful — values, functions, or objects
Example – useFetch Custom Hook:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
setError(null);
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
// Using the custom hook
function UserList() {
const { data, loading, error } = useFetch('https://api.example.com/users');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Another Example – useLocalStorage Hook:
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const saved = localStorage.getItem(key);
return saved ? JSON.parse(saved) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Usage
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
}

React Hooks Best Practices 2025
Follow these patterns for clean, efficient, and bug-free React Hooks code.
1. Rules of Hooks
Only call Hooks at the top level — never inside loops, conditions, or nested functions.
// ❌ Wrong
if (condition) {
const [state, setState] = useState(0);
}
// ✅ Correct
const [state, setState] = useState(0);
if (condition) {
// Use state here
}
2. Dependency Arrays
Always include all dependencies your useEffect uses to avoid stale data or unexpected behavior.
// ❌ Missing dependencies
useEffect(() => {
console.log(userId, name);
}, []); // Should include userId and name
// ✅ Complete dependencies
useEffect(() => {
console.log(userId, name);
}, [userId, name]);
3. Avoid Unnecessary Re-renders
Use useMemo and useCallback to prevent expensive computations and unnecessary re-renders.
import { useMemo, useCallback } from 'react';
function ExpensiveComponent({ items, onItemClick }) {
// Memoize expensive calculation
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);
// Memoize callback
const handleClick = useCallback((id) => {
onItemClick(id);
}, [onItemClick]);
return <div>Total: {total}</div>;
}
4. Keep Hooks Simple
Break complex Hooks into smaller, reusable custom Hooks for better readability and maintenance.
5. Use ESLint Plugin
Install eslint-plugin-react-hooks for automatic checking of Hook rules and dependency arrays.

Common React Hooks Mistakes
Mistake 1: Infinite Loops
When your useEffect updates state without a dependency array, it causes infinite re-renders.
// ❌ Creates infinite loop
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // Re-renders, calls useEffect again
});
// ✅ Fixed
useEffect(() => {
setCount(c => c + 1);
}, []); // Only runs once
Mistake 2: Stale Closures
Closures in useEffect can capture outdated state values. Always use functional updates to avoid stale data.
// ❌ Captures old count value
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount(count + 1); // Always uses initial count
}, 1000);
}, []);
// ✅ Use functional update
useEffect(() => {
setInterval(() => {
setCount(c => c + 1); // Uses current count
}, 1000);
}, []);
Mistake 3: Missing Cleanup
Forgetting to clean up subscriptions or timers can cause memory leaks. Always return a cleanup function in useEffect.
// ❌ Memory leak
useEffect(() => {
const subscription = subscribe();
}, []);
// ✅ Cleanup subscription
useEffect(() => {
const subscription = subscribe();
return () => subscription.unsubscribe();
}, []);
React Hooks vs Class Components
Should you migrate to Hooks?
- For new code: Absolutely use Hooks — they are simpler, cleaner, and more modern.
- For existing class components: No urgent need to rewrite. Hooks and classes work perfectly together.
Migration Example
Class Component:
// Class component
class UserProfile extends React.Component {
state = { user: null, loading: true };
componentDidMount() {
fetch('/api/user')
.then(res => res.json())
.then(user => this.setState({ user, loading: false }));
}
render() {
const { user, loading } = this.state;
return loading ? <div>Loading...</div> : <div>{user.name}</div>;
}
}
Equivalent Functional Component Using Hooks:
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(user => {
setUser(user);
setLoading(false);
});
}, []);
return loading ? <div>Loading...</div> : <div>{user.name}</div>;
}
Result: The Hooks version is shorter, clearer, and more maintainable while offering the same functionality.
Frequently Asked Questions
Can I use Hooks in class components?
No. Hooks only work in functional components. But you can use functional components with Hooks alongside existing class components.
Do I need to rewrite all class components?
No. Hooks and classes work together. Rewrite gradually or keep classes if they work fine.
Are Hooks slower than classes?
No. Performance is comparable. Sometimes Hooks are slightly faster due to less overhead.
Can I make my own Hooks?
Yes! Custom Hooks are powerful for sharing stateful logic. Follow the “use” naming convention.
What React version do I need?
React 16.8 or higher. In 2025, this shouldn’t be an issue.
Can Hooks replace Redux?
For simple state, yes. Complex applications might still benefit from Redux or Context + useReducer.
Conclusion: Master React Hooks 2025
React Hooks are modern React’s foundation. Learning Hooks means learning how React works in 2025.
Start with useState for simple state. Master useEffect for side effects. Use useContext for global data. Try useReducer for complex state. Build custom Hooks for reusable logic.
Practice with real projects. Copy these examples. Modify them. Break them. Fix them. That’s how you actually learn.
Avoid common mistakes. Follow best practices. Use ESLint plugin. Read error messages carefully.
Modern React development expects Hook proficiency. Master them now. Your future React code will thank you.
Related React Resources
- React Performance Optimization 2025
- Best React UI Libraries 2025
- Next.js Complete Guide
- React vs Vue vs Angular
Latest Post
- Bijan Robinson Wiki, Age, Stats, Net Worth, Family & Record – The Ultimate 2025 Profile
- Travis Kelce Wiki, Age, Biography, Net Worth, Taylor Swift Engagement & Career – The Ultimate 2025 Profile
- React Hooks Tutorial 2025: Complete Beginner to Advanced Guide with Examples
- Aryna Sabalenka Wiki, Age, Biography, Net Worth, Boyfriend, Family & Titles – The Ultimate 2025 Profile
- WordPress Speed Optimization 2025: 12 Proven Ways Load Under 1 Second Fast
Disclaimer
Code examples tested with React 18. Syntax may change in future React versions. Always check official React documentation for latest best practices.
