useEffect in React

Understanding useEffect in React — The Lifecycle of Functional Components

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 useEffect maps to class component lifecycle methods
  • How to use useEffect for 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 ComponentFunctional Component
componentDidMountuseEffect(() => {...}, [])
componentDidUpdateuseEffect(() => {...}, [state])
componentWillUnmountcleanup 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 useEffect replaces lifecycle methods
  • Used useEffect to 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 useCallbackRead 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.

Let's have a discussion!

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from TACETRA

Subscribe now to keep reading and get access to the full archive.

Continue reading