Step 4
React

Step 4: “My First API Call in React: Mastering the useEffect Hook”

1. 🧠 Key Concepts

a) What is a “Side Effect”?

A React component’s main job is to be a “pure” function: it takes props and state and returns JSX.

A side effect is anything your component does that affects the “outside world” — anything besides just returning JSX.

Common examples of side effects:

  • Fetching data from an API (the most common one!)
  • Setting a timer (setTimeout or setInterval)
  • Manually changing the HTML document (like document.title = 'New Title')
  • Subscribing to a browser event (like window.resize)

The Problem: If you put a side effect (like a fetch call) directly in your component function, it will run every single time the component renders. If that fetch then sets state (which it will), it will cause a re-render, which will trigger the fetch again… creating an infinite loop that crashes your app.

We need a way to tell React: “Run this code, but not as part of the normal render. Run it after the component has rendered to the screen.”

b) The useEffect Hook: The Tool for Side Effects

This is our second major hook. The useEffect hook is the only correct place to put side effects in a functional component.

It takes two arguments:

  1. An “effect” function: The code you want to run (e.g., your fetch call).
  2. A “dependency” array: This is the most important part. It tells React when to re-run your effect.

JavaScript

import { useEffect, useState } from 'react';

useEffect(() => {
  // 1. The Effect Function:
  // This code runs AFTER the component renders
  console.log("Component has rendered. Effect is running.");

}, [ /* 2. The Dependency Array */ ]);

c) The Magic of the Dependency Array

This array is your control switch. It tells useEffect what to “watch.”

  • Case 1: [] (An Empty Array)
    • What it means: “Only run this effect function one single time, right after the component mounts (is shown on screen for the first time).”
    • Use Case: This is perfect for fetching initial data. You want to “fetch users” or “get blog posts” once when the page loads.
  • Case 2: [someVariable, someProp] (An Array with Values)
    • What it means: “Run this effect once on mount, AND again anytime the value of someVariable or someProp changes.”
    • Use Case: Imagine a <UserProfile> component that takes a userId prop. You want to re-fetch data if the userId changes. You would use useEffect(..., [userId]).
  • Case 3: (No Array / Omitted)
    • What it means: “Run this effect after every single render.”
    • Use Case: This is almost always a bug. This is what will cause the infinite loop if you’re setting state inside. Avoid this.

Warning: If you use a prop or state variable inside your useEffect, React’s linter (ESLint) will correctly warn you to add it to the dependency array. This is a safety feature.

d) Loading, Error, and Data States

A network request isn’t instant. It can be in one of three states:

  1. Loading: We’re waiting for the server to respond.
  2. Success: We got the data we wanted.
  3. Error: Something went wrong (API is down, bad URL, etc.).

Your UI must be able to handle all three. How? By using more state! We’ll use three useState hooks to track this.

  • const [data, setData] = useState(null);
  • const [isLoading, setIsLoading] = useState(true);
  • const [error, setError] = useState(null);

We’ll then use Conditional Rendering (from Step 3) to show the correct UI for each state.

e) The “Cleanup” Function

What happens if you start a timer (setInterval) and the component is removed from the screen? The timer will keep running forever in the background, creating a memory leak.

useEffect lets you return a function from your effect. This is called the “cleanup function.” React will run this function just before the component unmounts (is removed).

JavaScript

useEffect(() => {
  // Effect:
  const timerId = setInterval(() => {
    console.log("Timer is ticking...");
  }, 1000);

  // Cleanup:
  return () => {
    console.log("Cleaning up the timer!");
    clearInterval(timerId); // This stops the timer
  };
}, []);

For fetch requests, this is more advanced (you use an AbortController), but it’s crucial to know this exists for timers or subscriptions.


2. 🚀 Demo Project: Simple Data Fetcher

Let’s build a component that fetches a single user from the JSONPlaceholder public API and displays their name and email.

Coding Steps:

Step 1: Set up App.jsx Let’s start fresh in App.jsx. (You can delete the counter code).

JavaScript

// src/App.jsx

// 1. Import *both* useState and useEffect
import { useState, useEffect } from 'react';
import './MyStyles.css'; // We'll re-use our styles

function App() {
  // 2. Set up our three state variables
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  // 3. Set up the useEffect hook
  useEffect(() => {
    // We want this to run *only once* on mount.
    // So, we pass an empty dependency array: []

    // We define an async function *inside* the effect
    // We can't make the effect function itself async (it's bad practice)
    async function fetchData() {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
        
        if (!response.ok) {
          // If response is not 2xx (e.g., 404, 500)
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        
        const userData = await response.json();
        setData(userData); // We got the data!
        setError(null); // Clear any previous errors

      } catch (e) {
        // Handle any errors that occurred
        setError(e.message);
        setData(null); // Clear any previous data
        
      } finally {
        // This runs whether it succeeded or failed
        setIsLoading(false); // We are done loading
      }
    }

    // Call the function
    fetchData();

  }, []); // <-- 4. The empty dependency array. This is CRITICAL.

  // 5. Render the UI based on our state
  return (
    <div className="data-fetcher-container">
      <h1>User Data</h1>
      
      {/* Case 1: We are loading */}
      {isLoading && <p className="loading">Loading user data...</p>}
      
      {/* Case 2: We have an error */}
      {error && <p className="error">Error: {error}</p>}
      
      {/* Case 3: We have data! */}
      {/* We check for 'data' to make sure it's not null */}
      {data && (
        <div className="user-data">
          <p><strong>Name:</strong> {data.name}</p>
          <p><strong>Email:</strong> {data.email}</p>
          <p><strong>Phone:</strong> {data.phone}</p>
        </div>
      )}
    </div>
  );
}

export default App;

Step 2: Add Styles Open src/MyStyles.css and paste this in.

CSS

/* src/MyStyles.css */

.data-fetcher-container {
  width: 400px;
  margin: 100px auto;
  padding: 30px;
  background-color: #ffffff;
  border-radius: 10px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.data-fetcher-container h1 {
  text-align: center;
  color: #333;
  margin-top: 0;
}

.loading {
  font-size: 1.2rem;
  font-weight: 500;
  color: #555;
  text-align: center;
}

.error {
  font-size: 1.1rem;
  font-weight: 500;
  color: #D8000C; /* Red */
  background-color: #FFD2D2;
  padding: 10px;
  border-radius: 5px;
  text-align: center;
}

.user-data {
  background-color: #f9f9f9;
  border: 1px solid #eee;
  border-radius: 8px;
  padding: 16px;
}

.user-data p {
  font-size: 1.1rem;
  margin: 8px 0;
}

Step 3: See the Result! Go to your browser. For a split second, you’ll see “Loading user data…”. Then, it will be replaced by the name, email, and phone number of “Leanne Graham” fetched live from the API.

To test the error state, try changing the URL in your fetch call to something that doesn’t exist (like .../users/99999) and save. You’ll see your error message appear.

<–Back to React

Leave a Reply

Your email address will not be published. Required fields are marked *