React Hooks revolutionized how we write React components. Introduced in React 16.8, hooks let you use state and other React features in functional components. Let's dive deep into the most important ones.
What Are React Hooks?
Hooks are functions that let you "hook into" React state and lifecycle features from function components. They were created to avoid the complexity of class components and make code reuse easier.
1. useState — Managing Local State
The most commonly used hook. It returns a state variable and a function to update it.
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(prev => prev - 1)}>Decrement</button>
</div>
);
}Pro Tip: When the new state depends on the previous state, always use the functional update form: setCount(prev => prev + 1).
2. useEffect — Side Effects & Lifecycle
useEffect handles side effects like fetching data, subscribing to events, or updating the DOM.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// Runs after every render where userId changes
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
// Cleanup function (like componentWillUnmount)
return () => {
// Cancel requests, clear timeouts here
};
}, [userId]); // Dependency array
}3. useContext — Avoiding Prop Drilling
Share data across components without passing props at every level.
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}4. useReducer — Complex State Logic
A great alternative to useState when state logic is complex.
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return initialState;
default: throw new Error('Unknown action');
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}5. useMemo & useCallback — Performance Optimization
// useMemo - memoize computed values
const expensiveResult = useMemo(() => {
return heavyComputation(data);
}, [data]);
// useCallback - memoize functions
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);6. Building Custom Hooks
Custom hooks let you extract component logic into reusable 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(data => { setData(data); setLoading(false); })
.catch(err => { setError(err); setLoading(false); });
}, [url]);
return { data, loading, error };
}
// Usage
function Posts() {
const { data, loading, error } = useFetch('/api/posts');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
return <ul>{data.map(post => <li key={post.id}>{post.title}</li>)}</ul>;
}Rules of Hooks
- Only call hooks at the top level (not inside loops, conditions, or nested functions)
- Only call hooks from React function components or custom hooks
- Custom hooks must start with "use"
Conclusion
React Hooks make your code more readable, reusable, and maintainable. Start with useState and useEffect, then gradually explore useContext, useReducer, and custom hooks as your app grows.