useEffect Hook — Side Effects
Level: Beginner
- What side effects are and why they need useEffect
- Running effects on component mount (empty dependency array)
- Running effects when props/state change (dependency array)
- Cleaning up effects (return cleanup function)
- Common effect patterns: fetching data, timers, subscriptions
- Avoiding infinite loops with dependencies
- useEffect for SMS screens: loading students, polling attendance, syncing with API
- Best practices and gotchas
Why This Matters
useEffect Hook — Side Effects is part of building maintainable React applications. You will use it when creating student dashboards, forms, tables, API-connected screens, routing flows, and reusable UI components.
useEffect handles side effects: fetching data, setting timers, subscribing to events, updating the document title.
The Problem
Beginners often write React code that works for a small demo but becomes difficult when data, forms, API calls, and reusable components grow. This lesson explains useEffect Hook — Side Effects in a way that helps you build predictable UI for real .NET Web API projects.
What is a Side Effect?
Side effects are things that happen outside the component: API calls, timers, DOM manipulation.
function StudentDetail({ studentId }) {
// ❌ Wrong — runs every render
fetch(`/api/students/${studentId}`)
.then(r => r.json())
.then(data => console.log(data));
return <div>Loading...</div>;
}
Calling fetch() on every render makes infinite API calls.
useEffect runs the effect at specific times, not every render.
Basic useEffect Syntax
useEffect(() => {
// Effect runs here
}, [dependencies]);
- Effect function: runs when dependencies change
- Dependency array: what triggers the effect
useEffect Patterns
Pattern 1: Run Once (On Mount)
function StudentDetail({ studentId }) {
const [student, setStudent] = useState(null);
useEffect(() => {
// Fetch when component mounts
fetch(`/api/students/${studentId}`)
.then(r => r.json())
.then(data => setStudent(data));
}, []); // Empty dependency = run once
if (!student) return <p>Loading...</p>;
return <div>{student.name}</div>;
}
Empty dependency array [] means: run once when component first appears.
Pattern 2: Run on Prop Change
function StudentDetail({ studentId }) {
const [student, setStudent] = useState(null);
useEffect(() => {
fetch(`/api/students/${studentId}`)
.then(r => r.json())
.then(data => setStudent(data));
}, [studentId]); // Re-run when studentId changes
if (!student) return <p>Loading...</p>;
return <div>{student.name}</div>;
}
When studentId prop changes, the effect runs again with new ID.
Pattern 3: Run Every Render
function StudentCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Clicked ${count} times`;
}); // No dependency array = run every render
return <button onClick={() => setCount(count + 1)}>Click me</button>;
}
Title updates every time component re-renders.
Pattern 4: Cleanup Function
Some effects need cleanup: unsubscribe, cancel requests, clear timers.
function StudentTimer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// Start timer
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// Cleanup: stop timer when component unmounts
return () => clearInterval(interval);
}, []);
return <p>Elapsed: {seconds}s</p>;
}
The cleanup function runs when component unmounts or before re-running the effect.
Real Examples
Example 1: Fetch Student Data
function StudentProfile({ studentId }) {
const [student, setStudent] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true; // Prevent state updates after unmount
const fetchStudent = async () => {
try {
const response = await fetch(`/api/students/${studentId}`);
const data = await response.json();
if (isMounted) {
setStudent(data);
setError(null);
}
} catch (err) {
if (isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchStudent();
// Cleanup
return () => {
isMounted = false;
};
}, [studentId]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <div>{student.name}</div>;
}
Example 2: Fetch Multiple Resources
function StudentDashboard({ studentId }) {
const [student, setStudent] = useState(null);
const [marks, setMarks] = useState([]);
const [attendance, setAttendance] = useState([]);
useEffect(() => {
// Fetch all data when studentId changes
Promise.all([
fetch(`/api/students/${studentId}`).then(r => r.json()),
fetch(`/api/students/${studentId}/marks`).then(r => r.json()),
fetch(`/api/students/${studentId}/attendance`).then(r => r.json())
]).then(([studentData, marksData, attendanceData]) => {
setStudent(studentData);
setMarks(marksData);
setAttendance(attendanceData);
});
}, [studentId]);
return (
<div>
<h1>{student?.name}</h1>
<p>Marks: {marks.length}</p>
<p>Attendance: {attendance.length}%</p>
</div>
);
}
Example 3: Listen to Browser Events
function StudentWindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
// Subscribe to resize event
window.addEventListener('resize', handleResize);
// Cleanup: unsubscribe
return () => window.removeEventListener('resize', handleResize);
}, []);
return <p>Width: {width}px</p>;
}
Example 4: Debounce Search
function StudentSearch() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
// Delay search to avoid too many requests
const timer = setTimeout(() => {
if (query.length > 0) {
fetch(`/api/students/search?q=${query}`)
.then(r => r.json())
.then(data => setResults(data));
} else {
setResults([]);
}
}, 500); // Wait 500ms after typing stops
// Cleanup: cancel previous search
return () => clearTimeout(timer);
}, [query]);
return (
<>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search students..."
/>
<ul>
{results.map(student => (
<li key={student.id}>{student.name}</li>
))}
</ul>
</>
);
}
Dependency Array Rules
❌ Wrong
// Running on every render (expensive)
useEffect(() => {
fetchData();
}); // No dependency array
// Running once (doesn't update when prop changes)
useEffect(() => {
fetchStudent(studentId);
}, []); // Missing studentId
// Not running but should
useEffect(() => {
fetch(`/api/students/${studentId}`);
}, []); // Should include studentId
✓ Correct
// Run once on mount
useEffect(() => {
fetchInitialData();
}, []);
// Run when studentId changes
useEffect(() => {
fetchStudent(studentId);
}, [studentId]);
// Run when either changes
useEffect(() => {
fetchTeacher(teacherId);
}, [teacherId, schoolId]);
Multiple Effects
One component can have multiple effects:
function StudentDetail({ studentId }) {
// Effect 1: Fetch student data
useEffect(() => {
fetch(`/api/students/${studentId}`)
.then(r => r.json())
.then(data => setStudent(data));
}, [studentId]);
// Effect 2: Fetch marks
useEffect(() => {
fetch(`/api/students/${studentId}/marks`)
.then(r => r.json())
.then(data => setMarks(data));
}, [studentId]);
// Effect 3: Update document title
useEffect(() => {
document.title = `${student?.name} - Details`;
}, [student]);
return <div>{student?.name}</div>;
}
Each effect handles one concern.
Key Takeaways
useEffectruns side effects at specific times[]= run once on mount[dependency]= run when dependency changes- Return cleanup function to unsubscribe/cancel
- Multiple effects are fine
- Next: Lists and rendering arrays
- Forgetting dependency array — Causes infinite loops
- Missing dependencies — Effect doesn't re-run when needed
- Not cleaning up — Memory leaks from timers/events
- Async in effect directly — Use async function inside effect
- Updating state inside cleanup — Can cause warnings
Add logging to understand when effects run:
useEffect(() => {
console.log('Effect running with studentId:', studentId);
fetchStudent(studentId);
return () => {
console.log('Cleanup running');
};
}, [studentId]);
Use ChatGPT, Claude, or Copilot to go deeper on useEffect Hook. Try these prompts:
"When should useEffect run vs. the render function?""What does an empty dependency array mean?""Why do you need cleanup functions?""Quiz me on useEffect patterns"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.
Quick Definitions
- useEffect Hook — Side Effects - The main React concept explained in this lesson.
- Component - A reusable function that returns UI.
- Props/state/effects - Core React ideas used to pass data, remember data, and run side effects.
- Real project usage - How this appears in forms, dashboards, routes, and API-connected pages.
Common Mistakes
- Copying React code without understanding data flow
- Mutating arrays or objects directly instead of creating new values
- Forgetting keys, dependencies, loading states, or error states where needed
- Putting too much logic in one component
- Not testing the screen with realistic School Management System data
Practice Task
Create a small React example using useEffect Hook — Side Effects. Keep it focused on one School Management System screen.
Suggested practice:
- Build a small component or page for students, attendance, marks, or fees.
- Pass realistic data into the component.
- Add one success state and one empty/error state where relevant.
- Explain the data flow in your own words.
- Rebuild the same example once without looking at the article.
Quick Revision
| Question | Answer |
|---|---|
| What is the main idea? | Understand and apply useEffect Hook — Side Effects in React. |
| Where is it used? | Student dashboards, forms, tables, routes, and API-connected screens. |
| What should beginners focus on? | Clear components, predictable data flow, and small examples. |
| What is the best debugging habit? | Check props, state, render output, and browser console step by step. |
useEffect Hook — Side Effects is a React concept used to build clear, reusable, and predictable user interfaces. I would explain the problem it solves, show a small component example, and mention a common mistake beginners should avoid.
It is used in screens like student lists, admission forms, attendance dashboards, marks reports, routing pages, and API-connected admin panels.