Popular Searches
Popular Course Categories
Popular Courses

Fetching Data with Effects

Fetching Data with Effects

React Side Effects and Lifecycle

Fetching Data with Effects in React JS

Fetching Data with Effects in React JS is a technique used to retrieve information from an API or external server and display it inside a React component. React's useEffect() Hook allows developers to perform data-fetching operations after a component renders and update the UI when the response is received.

Data fetching is commonly used in weather applications, e-commerce websites, dashboards, news applications, social media platforms, and user management systems.

Course Link: JustAcademy React JS Training Course

Demo Link: Register for React JS Course Demo


1. What Is Fetching Data with Effects?

Fetching data with Effects means using the useEffect() Hook to request information from an API when a component appears or when specific dependencies change. The fetched data is usually stored in state with useState() so React can display it on the screen.

For example, a weather application can fetch temperature information from a weather API and display the result to the user.

Key Concepts

  • useEffect(): Runs an Effect to synchronize a component with an external system, such as an API.
  • useState(): Stores API data, loading status, and error messages.
  • fetch(): Sends an HTTP request to retrieve data from a server.
  • JSON: A common format for exchanging data between a client and a server.
  • Dependency Array: Controls when an Effect needs to run again.
  • Cleanup: Prevents outdated requests from updating the component or cancels requests when appropriate.

2. What Is the useEffect() Hook?

The useEffect() Hook is a built-in React Hook used to synchronize a component with external systems. It can perform tasks such as fetching data, connecting to services, subscribing to events, and managing timers.

Syntax

useEffect(() => {
  // Perform the API request or other Effect
  return () => {
    // Optional cleanup
  };
}, [dependencies]);

Example

import { useEffect } from "react";

function App() {
  useEffect(() => {
    console.log("Component Effect executed");
  }, []);

  return <h1>Welcome to React JS</h1>;
}

export default App;

Explanation: The empty dependency array means the Effect is set up when the component is mounted. In development, React Strict Mode may run an additional setup-and-cleanup cycle to help identify bugs.


3. Why Is Data Fetching Required in React?

Most modern applications need information that is stored outside the frontend application. React can request this information from APIs and display the response dynamically.

  • To display real-time or frequently updated information.
  • To retrieve user profiles and account details.
  • To display products, prices, and categories.
  • To load articles, news, and blog posts.
  • To show data from databases through backend APIs.
  • To update the user interface when the requested data changes.

Real-World Example

In an e-commerce application, React can request product information from a backend API and display each product's name, price, description, and image.


4. Understanding the fetch() Method

The JavaScript fetch() method sends a network request to a specified URL. It returns a Promise that can be handled with .then() or async/await.

Syntax

fetch("https://api.example.com/data")
  .then(response => {
    if (!response.ok) {
      throw new Error("Request failed");
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error(error);
  });

Important Points

  • fetch() sends the request to the specified URL.
  • response.ok indicates whether the HTTP status is in the successful 200–299 range.
  • response.json() reads and parses a JSON response.
  • .catch() handles network errors and errors thrown in the preceding Promise chain.
  • fetch() does not automatically reject its Promise for HTTP errors such as 404 or 500, so check response.ok.

5. Fetching Data Using useEffect() and useState()

When fetching data in React, developers commonly use useEffect() to initiate the request and useState() to store the response.

Example: Fetch Users from an API

import { useEffect, useState } from "react";

function Users() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    async function fetchUsers() {
      try {
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/users"
        );

        if (!response.ok) {
          throw new Error("Failed to fetch users");
        }

        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err.message || "Something went wrong");
      } finally {
        setLoading(false);
      }
    }

    fetchUsers();
  }, []);

  if (loading) {
    return <p>Loading users...</p>;
  }

  if (error) {
    return <p>Error: {error}</p>;
  }

  return (
    <div>
      <h2>User List</h2>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.name} - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Users;

Explanation

  1. useState([]) initializes an empty array for user information.
  2. loading tracks whether the request is in progress.
  3. error stores an error message if the request fails.
  4. useEffect() starts the request after the component commits.
  5. fetch() sends a request to the sample users API.
  6. response.json() converts the response body into JavaScript data.
  7. setUsers(data) saves the fetched users in state.
  8. The component renders a loading message, an error message, or the user list as appropriate.
  9. The key prop helps React identify each list item.

6. Understanding the Data-Fetching Flow

React Component Renders
          |
          v
useEffect() Runs
          |
          v
API Request Starts
          |
          v
Server Returns Response
          |
          v
Check Response Status
          |
          v
Parse JSON Data
          |
          v
Update React State
          |
          v
Component Re-renders
          |
          v
Display API Data

Note: The request can fail, so a complete implementation should handle loading, success, and error states.


7. Understanding the Dependency Array

The dependency array determines when an Effect should be re-synchronized. Every reactive value used by the Effect must be included in the dependency array.

Dependency Array Behavior Typical Use
[] Runs the Effect on initial mounting, with an extra development-only setup-and-cleanup cycle possible in Strict Mode. Fetch initial data.
[userId] Runs when the component mounts and when userId changes. Fetch details for a selected user.
[category, page] Runs when either dependency changes. Fetch filtered or paginated products.
No dependency array Runs after every commit. Usually avoid for data fetching because it may cause repeated requests.

Example: Fetch Data When userId Changes

import { useEffect, useState } from "react";

function UserDetails({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    async function loadUser() {
      const response = await fetch(
        `https://jsonplaceholder.typicode.com/users/${userId}`
      );

      if (!response.ok) {
        throw new Error("Unable to load user");
      }

      const data = await response.json();
      setUser(data);
    }

    loadUser().catch(error => {
      console.error(error);
    });
  }, [userId]);

  return (
    <div>
      {user ? <h2>{user.name}</h2> : <p>Loading user...</p>}
    </div>
  );
}

export default UserDetails;

Explanation: Whenever the userId prop changes, React re-runs the Effect so that the component requests the correct user's information. This basic example logs errors; production code should also display a useful error message and protect against outdated responses.


8. Fetching Data Using async/await

async/await provides a readable way to work with Promises. It lets developers write asynchronous code in a style that resembles ordinary sequential JavaScript.

Example

useEffect(() => {
  async function getPosts() {
    try {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/posts"
      );

      if (!response.ok) {
        throw new Error("Unable to fetch posts");
      }

      const posts = await response.json();
      console.log(posts);
    } catch (error) {
      console.error("Fetching error:", error);
    }
  }

  getPosts();
}, []);

Important: Do not make the Effect callback itself async. An async function returns a Promise, but a useEffect() callback should return either nothing or a cleanup function. Define and call an async function inside the Effect instead.


9. Handling Loading, Success, and Error States

A reliable data-fetching component should handle three main states.

State Purpose Example UI
Loading Indicates that a request is in progress. Loading products...
Success Displays the returned data. Product list or user profile.
Error Explains that the request failed. Unable to load data. Please try again.

Example

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");

useEffect(() => {
  let ignore = false;

  async function loadData() {
    setLoading(true);
    setError("");

    try {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/posts/1"
      );

      if (!response.ok) {
        throw new Error("Unable to load data");
      }

      const result = await response.json();

      if (!ignore) {
        setData(result);
      }
    } catch (err) {
      if (!ignore) {
        setError(err.message || "An error occurred");
      }
    } finally {
      if (!ignore) {
        setLoading(false);
      }
    }
  }

  loadData();

  return () => {
    ignore = true;
  };
}, []);

Explanation: The cleanup function sets ignore to true when the Effect is cleaned up. This prevents an outdated request from updating state after the component has moved on or unmounted.


10. Preventing Race Conditions in Data Fetching

A race condition can occur when multiple requests are started and their responses arrive in a different order. An older request may finish after a newer request and overwrite the latest data.

Example Scenario

  • A user selects category A, starting request A.
  • The user quickly selects category B, starting request B.
  • Request B finishes first and displays category B.
  • Request A finishes later and could incorrectly replace the display with category A.

To prevent this, clean up the previous Effect and either ignore its result or cancel its request using AbortController.

Example Using AbortController

import { useEffect, useState } from "react";

function Posts() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState("");

  useEffect(() => {
    const controller = new AbortController();

    async function loadPosts() {
      try {
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/posts",
          { signal: controller.signal }
        );

        if (!response.ok) {
          throw new Error("Failed to load posts");
        }

        const data = await response.json();
        setPosts(data);
      } catch (err) {
        if (err.name !== "AbortError") {
          setError(err.message || "An error occurred");
        }
      }
    }

    loadPosts();

    return () => {
      controller.abort();
    };
  }, []);

  if (error) {
    return <p>{error}</p>;
  }

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

export default Posts;

Explanation: AbortController cancels the request when the Effect is cleaned up. The code handles an intentional abort separately so it is not displayed as an ordinary error.


11. Fetching Data When a User Clicks a Button

Not every request belongs in an Effect. If a request should happen specifically because a user clicks a button, the request generally belongs in that event handler. Use an Effect when the component needs to synchronize with external data based on its current state or props.

Example

import { useState } from "react";

function LoadPost() {
  const [post, setPost] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  async function handleLoadPost() {
    setLoading(true);
    setError("");

    try {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/posts/1"
      );

      if (!response.ok) {
        throw new Error("Unable to load post");
      }

      const result = await response.json();
      setPost(result);
    } catch (err) {
      setError(err.message || "An error occurred");
    } finally {
      setLoading(false);
    }
  }

  return (
    <div>
      <button onClick={handleLoadPost} disabled={loading}>
        {loading ? "Loading..." : "Load Post"}
      </button>
      {error && <p>{error}</p>}
      {post && <h3>{post.title}</h3>}
    </div>
  );
}

export default LoadPost;

Explanation: The API request starts only when the user clicks the button. Since the request is caused by a particular interaction, it does not need to be initiated by useEffect().


12. Common Mistakes in Data Fetching with Effects

Mistake Problem Correct Approach
Making the Effect callback async It returns a Promise instead of a cleanup function. Define an async function inside the Effect and call it.
Omitting required dependencies The Effect can use outdated values. Include all reactive values used by the Effect.
Not checking response status HTTP errors such as 404 may be treated as successful responses. Check response.ok.
Not handling errors Users may see a blank screen or unhandled errors. Use try/catch and show an error message.
Not handling loading state Users cannot tell whether the request is still running. Track loading state.
Fetching on every render Can produce repeated requests or an infinite loop. Use the correct dependency array.
Ignoring race conditions Older responses can overwrite newer data. Cancel requests or ignore outdated results.
Using an unstable object dependency The Effect may run more often than expected. Remove unnecessary object dependencies or create the object inside the Effect.

13. Best Practices for Fetching Data with Effects

  • Use useEffect() only when the request synchronizes the component with external data.
  • Use useState() to manage the response, loading indicator, and error message.
  • Always check response.ok before parsing the response as successful data.
  • Include every reactive value used by the Effect in its dependency array.
  • Use AbortController or an ignore flag when outdated requests must not affect the UI.
  • Display useful loading and error messages.
  • Use stable keys when rendering lists with .map().
  • Avoid unnecessary API calls and repeated state updates.
  • Keep API logic organized and consider reusable custom Hooks when several components need the same behavior.
  • For larger applications, consider framework data-fetching tools or libraries such as TanStack Query, SWR, or React Router data APIs to support caching and request coordination.
  • Remember that Effects run on the client, not during server rendering.

14. Advantages of Fetching Data with Effects

  • Enables components to retrieve external data.
  • Works naturally with React state and re-rendering.
  • Can automatically refetch when relevant dependencies change.
  • Supports loading and error states.
  • Can use cleanup logic to prevent stale responses from affecting the UI.
  • Helps build dynamic interfaces such as dashboards, product lists, and user profiles.

15. Limitations of Fetching Data with Effects

  • Requires extra code for loading, error, cancellation, and race-condition handling.
  • Does not provide caching automatically.
  • Can cause repeated requests if dependencies are incorrect.
  • Effects do not fetch data during server rendering.
  • Fetching data in parent and child components can create sequential network requests, also called network waterfalls.

For complex applications, use a framework's built-in data-fetching mechanism or a dedicated data-fetching library when appropriate.


16. Real-World Applications

Application Data Fetched
Weather App Temperature, humidity, forecast, and location data.
E-Commerce Website Product names, images, prices, and stock information.
News App Headlines, articles, categories, and publication dates.
Social Media App User profiles, posts, comments, and notifications.
Admin Dashboard Reports, sales statistics, user counts, and analytics.
Student Management System Student details, courses, attendance, and results.
Food Delivery App Restaurant listings, menus, delivery status, and order details.

17. Interview Questions and Answers

Q1. What is data fetching in React?

Answer: Data fetching is the process of requesting information from an external API or server and displaying it in a React component.

Q2. Why is useEffect() used for data fetching?

Answer: It allows a component to synchronize with an external system, such as a network API, after a render has been committed.

Q3. What is the purpose of useState() in data fetching?

Answer: It stores the fetched data and can also track loading and error states. Updating state causes React to render the updated UI.

Q4. Why do we use an empty dependency array?

Answer: An empty dependency array indicates that the Effect has no reactive dependencies and is set up on mounting. Development Strict Mode may perform an additional setup-and-cleanup cycle.

Q5. Can we make the useEffect() callback async?

Answer: No. An async function returns a Promise, while an Effect callback must return nothing or a cleanup function. Define an async function inside the Effect instead.

Q6. What is response.json()?

Answer: It reads the response body and parses JSON data into a JavaScript value. It returns a Promise.

Q7. Why should we check response.ok?

Answer: Because HTTP error responses such as 404 and 500 do not automatically cause fetch() to reject its Promise.

Q8. What is a race condition in data fetching?

Answer: It occurs when requests finish in a different order than they started, allowing an outdated response to overwrite newer data.

Q9. How can we prevent outdated requests from updating state?

Answer: Use an ignore flag in the cleanup function or cancel the request with AbortController.

Q10. Should every API request use useEffect()?

Answer: No. Requests that should happen because a component is synchronizing with external data may use an Effect. Requests triggered directly by user actions, such as clicking a button to load a report, usually belong in the event handler.

Q11. Does useEffect() run on the server?

Answer: No. Effects run on the client. Server-rendered applications may use framework-specific data-fetching mechanisms instead.

Q12. What are alternatives to manually fetching data in Effects?

Answer: Framework-provided data fetching, TanStack Query, SWR, and React Router data APIs are common alternatives that can help manage caching, request coordination, and loading states.


18. Practical Exercises

  1. Create a React component that fetches a list of users from an API and displays their names.
  2. Create a posts component that displays a loading message while data is being fetched.
  3. Add error handling to a product list component.
  4. Fetch a user's details whenever a userId prop changes.
  5. Create a button that fetches a random post when clicked.
  6. Use AbortController to cancel a request during Effect cleanup.
  7. Build a small weather application that fetches data from a suitable weather API.
  8. Create a product listing with category filtering and pagination.

19. Quick Revision Checklist

  • Understand the purpose of useEffect().
  • Know how to make an HTTP request using fetch().
  • Understand how to parse JSON responses.
  • Store fetched data using useState().
  • Manage loading, success, and error states.
  • Use the correct dependency array.
  • Check HTTP response status with response.ok.
  • Prevent race conditions using cleanup logic.
  • Understand when to use an Effect and when to use an event handler.
  • Know when framework data-fetching features or caching libraries are more suitable.

20. Official Documentation and Learning Resources


21. Conclusion

Fetching Data with Effects is an important React JS concept for building applications that retrieve information from external APIs. By combining useEffect(), useState(), fetch(), dependency arrays, loading and error states, and cleanup logic, developers can build interfaces that display API data reliably. For larger applications, framework data-fetching mechanisms or dedicated libraries may provide better caching, performance, and request management.

Learn More: Explore JustAcademy React JS Training

Book a Demo: Register for a React JS Course Demo

whatsapp