So far we’ve covered Introduction to React Hooks and useState in React. The useEffect hook is one of the most important hooks in React, letting you perform side effects such as fetching data, subscribing to events, or updating the DOM. In this tutorial, we’ll integrate useEffect into our Task Tracker App to load tasks from an API and learn how to manage cleanup to prevent memory leaks.
Let’s dive in this tutorial for useEffect in React.
What is useEffect?
The useEffect hook lets you perform side effects in functional components.
A side effect is anything that affects something outside the function:
- Fetching data from an API
- Subscribing to events
- Updating the document title
- Setting up timers
Before hooks, these were handled in class lifecycle methods like componentDidMount or componentDidUpdate.
What You’ll Learn in This Post
- How
useEffectmaps to class component lifecycle methods - How to use
useEffectfor fetching data and cleaning up side effects - Updating our Task Tracker App to load tasks from an API
Syntax of useEffect
useEffect(() => {
// Code that runs after render
return () => {
// Optional cleanup code (runs before unmount or next effect)
};
}, [dependencies]);
- First argument: a function that runs after the component renders
- Optional cleanup function: for removing subscriptions, clearing timers, etc.
- Dependency array: tells React when to re-run the effect
Lifecycle Methods vs useEffect
Here’s how common lifecycle methods map to useEffect:
| Class Component | Functional Component |
|---|---|
componentDidMount | useEffect(() => {...}, []) |
componentDidUpdate | useEffect(() => {...}, [state]) |
componentWillUnmount | cleanup inside useEffect |
Example: Changing Document Title
import React, { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]); // re-run when count changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
The page title updates whenever count changes.
Mini Project Step: Fetch Tasks from an API
We’ll now improve our Task Tracker App to load initial tasks from an API using useEffect.
We’ll use the free JSONPlaceholder API:https://jsonplaceholder.typicode.com/todos?_limit=5
Updated App.jsx
import React, { useState, useEffect } from "react";
function App() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState("");
// Fetch tasks when component mounts
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos?_limit=5")
.then((res) => res.json())
.then((data) => setTasks(data))
.catch((err) => console.error("Error fetching tasks:", err));
}, []); // [] = run once on mount
const addTask = (e) => {
e.preventDefault();
if (!newTask.trim()) return;
const task = { id: Date.now(), title: newTask };
setTasks([...tasks, task]);
setNewTask("");
};
return (
<div style={{ padding: "20px" }}>
<h1>Task Tracker</h1>
{/* Form to add tasks */}
<form onSubmit={addTask}>
<input
type="text"
placeholder="Enter new task"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
<button type="submit">Add Task</button>
</form>
{/* Render task list */}
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.title || task.text}</li>
))}
</ul>
</div>
);
}
export default App;
Now when you load the app, the first 5 tasks come from an API. You can still add new tasks with the form.
Common Pitfalls with useEffect
1. Forgetting the dependency array
useEffect(() => {
console.log("Runs every render!"); // infinite loop risk
});
2. Wrong dependencies
useEffect(() => {
setCount(count + 1); // ❌ infinite loop
}, [count]);
Fix: Use functional updates or rethink logic.
3. Forgetting cleanup (e.g., timers, event listeners)
useEffect(() => {
const timer = setInterval(() => console.log("tick"), 1000);
return () => clearInterval(timer); // cleanup
}, []);
What We’ve Achieved
- Learned how
useEffectreplaces lifecycle methods - Used
useEffectto fetch tasks from an API - Built our Task Tracker into a real data-driven app
Learning Path
Post 1: Introduction to React Hooks (Why They Matter + Starter Project) – Read Post-1
Learn what hooks are, why they replaced class components, and set up the starter Task Tracker App.
Post 2: Mastering useState in React – Read Post 2
Manage state in functional components. Add new tasks dynamically with useState.
Post 3: Understanding useEffect (Side Effects + API Calls) – Current Post
Fetch tasks from an API, mimic lifecycle methods, and clean up side effects.
Post 4: Using useRef in React (Not Just for DOM Access) – Read Post 4
Control focus on the input field and learn how to store values without re-renders.
Post 5: Creating Custom Hooks (useLocalStorage) – Read Post 5
Extract reusable logic into a custom hook that persists tasks across refreshes.
Post 6: Optimizing Performance with useMemo and useCallback – Read Post 6
Add a search feature and optimize it with memoization to prevent unnecessary re-renders.
Post 7 (Final): Building a Complete Task Tracker with React Hooks – Read Post 7
Add delete + toggle complete functionality, and wrap up with a fully functional app.
Discover more from TACETRA
Subscribe to get the latest posts sent to your email.