Lesson 46 of 60 – Fetching API Data
77%

Fetching API Data in React

Modern React applications often need to communicate with APIs to retrieve data from a server. The JavaScript fetch() function can be used to request data from an API and then display that data inside React components.

Note: API requests are asynchronous. In React, useEffect is commonly used when data should be fetched as a side effect of rendering, while useState can store the fetched data, loading state, and error state.

1. What is an API?

An API (Application Programming Interface) allows different software systems to communicate with each other.

A React application can request data from a server through an API.

React Application
       ↓
      API
       ↓
     Server
       ↓
     Data

2. Why Fetch API Data?

Many applications need data that is stored on a server rather than directly inside the React application.

Examples include:

  • Student information
  • Product information
  • Course information
  • Blog posts
  • User accounts
  • Orders

3. What is fetch()?

The JavaScript fetch() function is used to make HTTP requests and retrieve data from a URL.

fetch("https://example.com/api/students");

The request is asynchronous and returns a Promise.

4. Basic fetch() Syntax

fetch("API_URL")
    .then(response => response.json())
    .then(data => {
        console.log(data);
    });

The first then() receives the response, and response.json() converts a JSON response into JavaScript data.

5. JSON Data

Many APIs return data in JSON format.

{
    "id": 1,
    "name": "Rahul",
    "course": "React"
}

JSON is commonly used for exchanging structured data between a client and server.

6. Convert Response to JSON

The json() method reads the response body and parses JSON data.

fetch("/api/student")
    .then(response => response.json())
    .then(data => {
        console.log(data);
    });

7. Using fetch() in React

In React, API data is often fetched inside a useEffect Hook when the request should happen after the component is rendered.

import {
    useEffect
} from "react";

function Students() {

    useEffect(() => {

        fetch("/api/students")
            .then(response => response.json())
            .then(data => {
                console.log(data);
            });

    }, []);

    return <h2>Students</h2>;
}

8. Why useEffect?

Fetching data is an external operation. useEffect can be used when the request should occur as a side effect of the component.

useEffect(() => {

    // API request

}, []);

An empty dependency array commonly means the effect is set up after the initial render and is not re-run because of changing dependencies.

9. Store API Data with useState

Fetched data can be stored in React state.

import {
    useEffect,
    useState
} from "react";

function Students() {

    const [students, setStudents] = useState([]);

    useEffect(() => {

        fetch("/api/students")
            .then(response => response.json())
            .then(data => {
                setStudents(data);
            });

    }, []);

    return <h2>Students</h2>;
}

10. Display Fetched Data

After storing the data in state, you can display it in JSX.

{students.map(student => (
    <p key={student.id}>
        {student.name}
    </p>
))}

The map() method can be used when the API returns an array.

11. Loading State

API requests can take some time. A loading state can show the user that data is being requested.

const [loading, setLoading] = useState(true);

Initially, loading can be set to true.

12. Updating Loading State

fetch("/api/students")
    .then(response => response.json())
    .then(data => {
        setStudents(data);
        setLoading(false);
    });

After the data is received, the loading state can be changed to false.

13. Displaying a Loading Message

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

This provides immediate feedback while the request is in progress.

14. Error State

API requests can fail because of network problems, server errors, or invalid URLs. An error state can store information about a failed request.

const [error, setError] = useState(null);

15. Handling Errors with catch()

fetch("/api/students")
    .then(response => response.json())
    .then(data => {
        setStudents(data);
    })
    .catch(error => {
        setError(error.message);
    });

The catch() method can handle rejected Promises.

16. Displaying an Error

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

This allows the application to show an appropriate message when a request fails.

17. Check response.ok

The fetch() Promise does not automatically reject for every HTTP error status. You can check response.ok before parsing the data.

fetch("/api/students")
    .then(response => {

        if (!response.ok) {
            throw new Error("Request failed");
        }

        return response.json();
    });

18. Complete Basic Fetch Example

import {
    useEffect,
    useState
} from "react";

function Students() {

    const [students, setStudents] = useState([]);

    useEffect(() => {

        fetch("/api/students")
            .then(response => response.json())
            .then(data => {
                setStudents(data);
            });

    }, []);

    return (
        <div>

            <h2>Students</h2>

            {students.map(student => (
                <p key={student.id}>
                    {student.name}
                </p>
            ))}

        </div>
    );
}

export default Students;

19. Fetching a Single Object

An API may return a single object instead of an array.

{
    "id": 101,
    "name": "Rahul",
    "course": "React"
}

The object can be stored directly in state.

const [student, setStudent] = useState(null);

20. Displaying an Object

{student && (
    <div>

        <h2>
            {student.name}
        </h2>

        <p>
            Course: {student.course}
        </p>

    </div>
)}

The condition prevents the component from trying to access properties before the data has been loaded.

21. Fetching Data with Async/Await

The async/await syntax can make asynchronous code easier to read.

async function getStudents() {

    const response = await fetch(
        "/api/students"
    );

    const data = await response.json();

    console.log(data);
}

22. Async Function Inside useEffect

An inner asynchronous function can be defined inside an effect.

useEffect(() => {

    async function loadStudents() {

        const response = await fetch(
            "/api/students"
        );

        const data = await response.json();

        setStudents(data);
    }

    loadStudents();

}, []);

23. Complete Async/Await Example

import {
    useEffect,
    useState
} from "react";

function Students() {

    const [students, setStudents] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {

        async function loadStudents() {

            try {

                const response = await fetch(
                    "/api/students"
                );

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

                const data = await response.json();

                setStudents(data);

            } catch (err) {

                setError(err.message);

            } finally {

                setLoading(false);

            }
        }

        loadStudents();

    }, []);

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

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

    return (
        <div>

            <h2>Students</h2>

            {students.map(student => (
                <p key={student.id}>
                    {student.name}
                </p>
            ))}

        </div>
    );
}

export default Students;

24. Fetching Data Using a Route Parameter

API requests can use route parameters to load specific data.

const { id } = useParams();

useEffect(() => {

    fetch(`/api/students/${id}`)
        .then(response => response.json())
        .then(data => {
            setStudent(data);
        });

}, [id]);

For example, the URL may be:

/students/101

25. Fetching Data for a Product

const { id } = useParams();

useEffect(() => {

    async function loadProduct() {

        const response = await fetch(
            `/api/products/${id}`
        );

        const data = await response.json();

        setProduct(data);
    }

    loadProduct();

}, [id]);

The parameter identifies which product should be requested.

26. Common Fetch API Mistakes

  • Forgetting to call response.json().
  • Using the wrong API URL.
  • Forgetting to store the returned data.
  • Not handling loading state.
  • Not handling errors.
  • Ignoring response.ok when HTTP errors matter.
  • Creating unnecessary repeated requests.
  • Forgetting relevant dependencies when an effect depends on changing data.

27. API Data Workflow

A common React API workflow is:

Component Renders
       ↓
useEffect
       ↓
fetch()
       ↓
Server/API
       ↓
Response
       ↓
response.json()
       ↓
setState()
       ↓
Component Re-renders
       ↓
Display Data

28. Loading, Success, and Error States

A complete API interface commonly needs three states:

  • Loading: Data is being requested.
  • Success: Data was received.
  • Error: The request failed.
Loading → Request → Success
                  ↘ Error

29. Complete Student API Example

import {
    useEffect,
    useState
} from "react";

function Students() {

    const [students, setStudents] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState("");

    useEffect(() => {

        async function fetchStudents() {

            try {

                const response = await fetch(
                    "/api/students"
                );

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

                const data = await response.json();

                setStudents(data);

            } catch (err) {

                setError(err.message);

            } finally {

                setLoading(false);

            }
        }

        fetchStudents();

    }, []);

    if (loading) {
        return (
            <h3>
                Loading students...
            </h3>
        );
    }

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

    return (
        <div>

            <h2>Student List</h2>

            {students.map(student => (

                <div
                    key={student.id}
                    className="card mt-3"
                >

                    <div className="card-body">

                        <h4>
                            {student.name}
                        </h4>

                        <p>
                            Course: {student.course}
                        </p>

                    </div>

                </div>

            ))}

        </div>
    );
}

export default Students;

30. Fetch API Best Practices

  • Keep API URLs organized.
  • Use useEffect for appropriate data-fetching effects.
  • Store fetched data in state when it affects the UI.
  • Show a loading state while waiting for data.
  • Handle request errors.
  • Check the HTTP response when appropriate.
  • Convert JSON responses using response.json().
  • Use route parameters when fetching individual resources.
  • Keep API-related code easy to maintain.
  • Do not make unnecessary repeated requests.
fetch()
   ↓
Response
   ↓
JSON
   ↓
State
   ↓
UI

📌 Key Points

  • fetch() is used to make HTTP requests.
  • APIs commonly return data in JSON format.
  • response.json() converts JSON response data into JavaScript data.
  • useEffect is commonly used for data-fetching side effects.
  • useState can store fetched data.
  • Loading and error states improve the user experience.
  • response.ok can be checked before processing a response.
  • Route parameters can be used to fetch specific resources.
  • Both Promise syntax and async/await can be used with fetch.

🧠 Quick Quiz

Question: Which JavaScript function is commonly used to request data from an API?