Modern React development requires Hooks. They’re the recommended way to write React components. Mastering Hooks will significantly improve your React skills.
useState Hook Deep Dive
The useState Hook is the simplest and most commonly used Hook. It lets you add state to functional components with a clean, intuitive API.
Basic Syntax
useState returns an array with two elements: the current state value and a function to update it.
Simple Counter Example
const [count, setCount] = useState(0);return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Multiple State Variables
You can use multiple useState hooks in a single component:
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>
);
}
Updating Objects and Arrays
When updating objects or arrays, create new objects/arrays rather than mutating:
const [user, setUser] = useState({ name: ‘John’, age: 30 });
setUser({ …user, age: 31 });// Correct: Create new array
const [items, setItems] = useState([]);
setItems([…items, newItem]);
useEffect Hook for Side Effects
useEffect lets you perform side effects in functional components: data fetching, subscriptions, manual DOM changes, and more.
Basic Syntax
// Side effect code runs after renderreturn () => {
// Cleanup function (optional)
};
}, [dependencies]);
Data Fetching Example
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);useEffect(() => {
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]);if (loading) return <p>Loading…</p>;
return <div><p>{user?.name}</p></div>;
}
Dependency Array Explained
| Dependency Array | When Effect Runs | Use Case |
|---|---|---|
| No array | After every render | Rarely needed, can cause issues |
| [] | Only on mount | Initialize data, setup subscriptions |
| [dependency] | When dependency changes | React to prop/state changes |
Cleanup Functions
Return a cleanup function to remove subscriptions or cleanup timers:
const handleResize = () => console.log(‘Resized’);
window.addEventListener(‘resize’, handleResize);return () => {
window.removeEventListener(‘resize’, handleResize);
};
}, []);
useContext for Global State
useContext allows you to access context values without prop drilling through many levels of components.
Creating and Providing Context
import { createContext, useState } from ‘react’;export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState(‘light’);
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
Consuming Context
import { ThemeContext } from ‘./ThemeProvider’;function Header() {
const { theme, setTheme } = useContext(ThemeContext);return (
<header style={{ background: theme === ‘dark’ ? ‘#333’ : ‘#fff’ }}>
<button onClick={() => setTheme(theme === ‘dark’ ? ‘light’ : ‘dark’)}>
Toggle Theme
</button>
</header>
);
}
Building Custom Hooks
Custom Hooks are JavaScript functions that use other Hooks. They let you extract logic and reuse it across components.
Rules for Custom Hooks
- Must start with “use” (e.g., useFetch, useWindowSize)
- Can only call other Hooks
- Must follow the Rules of Hooks
useFetch Custom Hook
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url);
const json = await res.json();
setData(json);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);return { data, loading, error };
}
Using Your Custom Hook
const { data: posts, loading } = useFetch(‘https://api.example.com/posts’);if (loading) return <p>Loading…</p>;
return (
<ul>
{posts?.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
);
}
Rules of Hooks
React enforces two important rules about Hooks through a linter plugin:
1. Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions:
❌ Don’t
const [state] = useState(0);
}
✅ Do
if (condition) {
// use state here
}
2. Only Call Hooks from React Functions
Only call Hooks from React components or custom Hooks:
❌ Don’t
const [state] = useState(0);
}
✅ Do
const [state] = useState(0);
}
Best Practices
1. Separate Concerns with Multiple Effects
// Fetch data
}, [dep1]);useEffect(() => {
// Setup event listener
}, [dep2]);
2. Handle Loading and Error States
if (error) return <ErrorMessage error={error} />;
return <DataDisplay data={data} />;
3. Extract Complex Logic into Custom Hooks
Keep components clean by extracting complex logic into reusable custom Hooks. This improves readability and maintainability.
4. Use Proper Keys in Lists
{items.map(item => <Item key={item.id} item={item} />)}// Avoid: Index as key
{items.map((item, i) => <Item key={i} item={item} />)}