React 19 Notes
Use AbortController to cancel in-flight fetches when components unmount. For data-heavy apps consider TanStack Query; for this series, useEffect + fetch teaches the fundamentals clearly.
This lesson explains React useEffect — the hook for side effects that run after render: fetching data, timers, DOM subscriptions, and syncing with external systems. You will fetch JSON from a public API and clean up resources correctly.
Prerequisites: Lessons 1–4, especially useState. Estimated time: 50–60 minutes.
1. What Is a Side Effect?
A side effect is work that reaches outside the component’s render output: network requests, localStorage, document.title, WebSocket listeners, or setInterval. React expects render to be pure — effects belong in useEffect.

2. useEffect Syntax
import { useEffect, useState } from 'react';
function PageTitle({ title }) {
useEffect(() => {
document.title = title;
}, [title]);
return <h1>{title}</h1>;
}
The second argument is the dependency array. React re-runs the effect when any dependency changes. An empty array [] runs once after mount (like componentDidMount).

3. Fetch Data on Mount
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function loadUsers() {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
if (!res.ok) throw new Error('Failed to fetch');
const data = await res.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
loadUsers();
}, []);
if (loading) return <p>Loading…</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
This pattern — loading, error, and data state — is standard in production React apps and frameworks like Next.js.
4. Cleanup Functions
Return a function from useEffect to clean up before the effect runs again or when the component unmounts:
useEffect(() => {
const id = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(id);
}, []);
Always clean up subscriptions, timers, and event listeners to prevent memory leaks — a core topic in any react useeffect tutorial.
5. Dependency Array Pitfalls
- Missing deps — stale closures; ESLint
react-hooks/exhaustive-depswarns you - Object/array deps — new reference every render causes infinite loops; memoize or lift state
- No array — effect runs after every render (rarely what you want)
6. Abort Fetch on Unmount (React 19)
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then((res) => res.json())
.then(setData)
.catch((err) => {
if (err.name !== 'AbortError') setError(err.message);
});
return () => controller.abort();
}, [url]);
Aborting prevents setting state on an unmounted component — especially important in React Strict Mode where effects run twice in development.
7. Next Steps
Effects connect React to the outside world. Next you will build controlled forms with state and validation patterns used in real apps.
Series: Lesson 4 · Lesson 6 — Forms
Frequently Asked Questions
When should I use useEffect?
Use it when you need to synchronize with something outside React’s render — APIs, browser APIs, third-party widgets, or timers.
Can I fetch data without useEffect?
Yes. React 19 and frameworks like Next.js support fetching in Server Components or with libraries like TanStack Query. useEffect fetch is still the standard pattern in client-only Vite SPAs.
Why does my useEffect run twice in development?
React Strict Mode intentionally double-invokes effects in development to surface cleanup bugs. Production behavior runs once per dependency change.
Want live React or frontend classes? Join Alkademy for instructor-led React and JavaScript courses with hands-on projects.

Leave a Reply