24 minLesson 17 of 40
React Fundamentals
Hooks: useState & useEffect
Hooks: useState & useEffect
Hooks are functions that let you use React features in function components. useState manages local state; useEffect handles side effects like data fetching, subscriptions, and DOM interactions.
useState
import { useState } from "react";
// Basic counter
const [count, setCount] = useState(0);
// Object state
const [user, setUser] = useState({ name: "", email: "" });
// Array state
const [items, setItems] = useState([]);
// Boolean toggle
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(prev => !prev);
// Lazy initialization (expensive initial value computed once)
const [data, setData] = useState(() => JSON.parse(localStorage.getItem("data") ?? "[]"));
useEffect
useEffect runs after render. Use it for:
- Fetching data
- Setting up subscriptions
- Updating the document title
- Interacting with browser APIs
import { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// This runs after every render where userId changed
let cancelled = false; // prevent race conditions
setLoading(true);
setError(null);
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => {
if (!cancelled) {
setUser(data);
setLoading(false);
}
})
.catch(err => {
if (!cancelled) {
setError(err.message);
setLoading(false);
}
});
// Cleanup function — runs before next effect or unmount
return () => { cancelled = true; };
}, [userId]); // dependency array — re-run when userId changes
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
if (!user) return null;
return <div>{user.name}</div>;
}
Dependency Array
// Run on every render (usually wrong)
useEffect(() => {
document.title = `${count} items`;
});
// Run once on mount
useEffect(() => {
analytics.trackPageView();
}, []);
// Run when specific values change
useEffect(() => {
fetchUsers(filters);
}, [filters]); // re-fetch when filters change
// Run when multiple values change
useEffect(() => {
updateChart(data, options);
}, [data, options]);
Common Patterns
Document Title
useEffect(() => {
document.title = `${count} notifications | MyApp`;
return () => { document.title = "MyApp"; };
}, [count]);
Resize Observer
useEffect(() => {
function handleResize() {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
}
window.addEventListener("resize", handleResize);
handleResize(); // call immediately
return () => window.removeEventListener("resize", handleResize);
}, []); // only mount/unmount
Debounced Search
function SearchComponent() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
if (!query.trim()) { setResults([]); return; }
const timer = setTimeout(async () => {
const data = await fetch(`/api/search?q=${query}`).then(r => r.json());
setResults(data);
}, 300);
return () => clearTimeout(timer); // cancel on new keystroke
}, [query]);
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
</div>
);
}
Rules of Hooks
- Only call hooks at the top level — never inside if, loops, or nested functions
- Only call hooks in React functions — not in regular JS functions
// ❌ Wrong
function Component({ condition }) {
if (condition) {
const [state, setState] = useState(0); // conditional hook — never do this!
}
}
// ✅ Correct
function Component({ condition }) {
const [state, setState] = useState(0); // always called
if (!condition) return null;
return <div>{state}</div>;
}
Next lesson: Lists, Keys & Conditional Rendering — rendering dynamic data in React.
📱
Get Notes Free →Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises