React Hooks - Modern React State Management Explained
React Hooks, introduced in React 16.8, revolutionized functional components by bringing state and lifecycle features without classes.
From useState for simple state to useEffect for side effects and custom hooks for reusable logic, Hooks make React code cleaner and more intuitive.
This tutorial covers essential React Hooks with practical examples to help you build modern React applications confidently.
useState Hook
useState is the foundation of state management in functional components, returning an array with current state and a setter function.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [todos, setTodos] = useState([]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
Always use functional updates for state based on previous state to avoid stale closures.
useEffect Hook
useEffect handles side effects in functional components, replacing componentDidMount, componentDidUpdate, and componentWillUnmount.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
// Cleanup function
return () => {
console.log('Cleanup');
};
}, [userId]); // Dependency array
return <div>{user?.name}</div>;
}
The dependency array controls when useEffect runs - empty array for mount only, specific values for targeted updates.
useRef and useContext
useRef creates mutable refs for DOM access or storing values across renders. useContext provides global state without prop drilling.
// ThemeContext.js
const ThemeContext = createContext();
// useRef example
function TextInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
// useContext example
function Button() {
const theme = useContext(ThemeContext);
return <button style={theme}>Click me</button>;
}
useReducer Hook
useReducer is perfect for complex state logic, similar to Redux but local to components.
function todosReducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, action.payload];
case 'DELETE':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todosReducer, []);
const addTodo = (text) => {
dispatch({ type: 'ADD', payload: { id: Date.now(), text } });
};
return (
<div>
{todos.map(todo => (
<div key={todo.id}>
{todo.text}
<button onClick={() => dispatch({ type: 'DELETE', id: todo.id })}>
Delete
</button>
</div>
))}
</div>
);
}
Custom Hooks
Custom Hooks let you extract reusable stateful logic into independent functions.
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
// Usage
function UserList() {
const { data: users, loading, error } = useFetch('/api/users');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
Custom Hooks must start with 'use' and can call other Hooks.
Other Essential Hooks
- useMemo for expensive calculations and preventing unnecessary re-renders
- useCallback for memoizing functions passed to child components
- useLayoutEffect for DOM mutations before browser paint
- useImperativeHandle for exposing component methods to parents
- useTransition for marking non-urgent state updates
- useDeferredValue for debouncing expensive renders
function ExpensiveComponent({ items }) {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return <div>Total: {expensiveValue}</div>;
}
Hooks Best Practices
- Only call Hooks at the top level - never in loops, conditions, or nested functions
- Keep effects small and focused on single concerns
- Use ESLint plugin react-hooks to catch mistakes
- Extract logic into custom Hooks for reusability
- Always provide exhaustive dependencies in useEffect/useCallback/useMemo
Conclusion
React Hooks have become the standard way to build React applications, eliminating the need for class components in most cases.
Mastering useState, useEffect, useReducer, and custom Hooks will make you a proficient React developer capable of building complex applications.
Practice these patterns in real projects and explore the React documentation for advanced patterns and optimizations.
Codecrown