Lesson 102 of 158 – React Native API Loading State
65%

React Native API Loading State

API requests take some time because data must travel between the React Native application and the server. During this time, the application should show a loading indicator so the user knows that the request is being processed.

Note: In this lesson, we will learn how to manage loading states while calling REST APIs from React Native using Fetch.

1. What is a Loading State?

A loading state tells the application that an API request is currently running.

Start Request
      ↓
Loading = true
      ↓
API Request
      ↓
Receive Response
      ↓
Loading = false

2. Why is Loading Important?

  • Shows the user that something is happening.
  • Prevents confusion during slow requests.
  • Helps prevent duplicate submissions.
  • Improves the user experience.
  • Makes API-based screens easier to understand.

3. useState for Loading

React Native can use useState to store the loading status.

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

Initially, loading is false because no API request is running.

4. Starting the Loading State

Before starting an API request, set loading to true.

setLoading(true);

const response = await fetch(
    API_URL
);

5. Stopping the Loading State

After the API request finishes, set loading back to false.

setLoading(true);

const response = await fetch(
    API_URL
);

setLoading(false);

However, using finally is safer because it also handles errors.

6. Using finally()

setLoading(true);

try {

    const response = await fetch(
        API_URL
    );

} catch (error) {

    console.log(error);

} finally {

    setLoading(false);

}

The finally block runs whether the request succeeds or fails.

7. ActivityIndicator

React Native provides ActivityIndicator for displaying a loading spinner.

import {
    ActivityIndicator
} from "react-native";

Example:

<ActivityIndicator
    size="large"
/>

8. Conditional ActivityIndicator

{loading && (
    <ActivityIndicator
        size="large"
    />
)}

The indicator is displayed only when loading is true.

9. Loading Text

You can also show text along with the loading indicator.

{loading && (
    <>
        <ActivityIndicator
            size="large"
        />

        <Text>
            Loading...
        </Text>
    </>
)}

10. Loading During GET Request

async function loadStudents() {

    setLoading(true);

    try {

        const response = await fetch(
            API_URL
        );

        const result =
            await response.json();

        console.log(result);

    } catch (error) {

        console.log(error);

    } finally {

        setLoading(false);

    }
}

11. Loading During POST Request

async function addStudent() {

    setLoading(true);

    try {

        const response = await fetch(
            API_URL,
            {
                method: "POST",

                headers: {
                    "Content-Type":
                        "application/json"
                },

                body: JSON.stringify({
                    name: "Rahul"
                })
            }
        );

        const result =
            await response.json();

        console.log(result);

    } finally {

        setLoading(false);

    }
}

12. Loading During PUT Request

async function updateStudent(id) {

    setLoading(true);

    try {

        const response = await fetch(
            `${API_URL}?id=${id}`,
            {
                method: "PUT",

                headers: {
                    "Content-Type":
                        "application/json"
                },

                body: JSON.stringify({
                    name: "Rahul"
                })
            }
        );

        const result =
            await response.json();

        console.log(result);

    } finally {

        setLoading(false);

    }
}

13. Loading During DELETE Request

async function deleteStudent(id) {

    setLoading(true);

    try {

        const response = await fetch(
            `${API_URL}?id=${id}`,
            {
                method: "DELETE"
            }
        );

        const result =
            await response.json();

        console.log(result);

    } finally {

        setLoading(false);

    }
}

14. Disable Repeated Submission

Loading can be used to prevent the same operation from being submitted multiple times.

async function handleSubmit() {

    if (loading) {
        return;
    }

    setLoading(true);

    try {

        // API request

    } finally {

        setLoading(false);

    }
}

15. Loading State on a Button

<Button
    title={
        loading
            ? "Saving..."
            : "Save Student"
    }
    onPress={handleSubmit}
    disabled={loading}
/>

The button becomes disabled while the request is running.

16. Loading and Error State

A real API screen commonly needs both loading and error states.

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

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

17. Handling API Errors

async function loadData() {

    setLoading(true);
    setError("");

    try {

        const response = await fetch(
            API_URL
        );

        if (!response.ok) {

            throw new Error(
                "API request failed"
            );

        }

        const result =
            await response.json();

        console.log(result);

    } catch (error) {

        setError(
            error.message
        );

    } finally {

        setLoading(false);

    }
}

18. Displaying an Error Message

{error !== "" && (
    <Text>
        {error}
    </Text>
)}

This allows the user to see an error when the API request fails.

19. Loading, Error and Data States

A typical API screen can have three important states.

Loading
   ↓
Show Spinner

Success
   ↓
Show Data

Error
   ↓
Show Error Message

20. Loading with useEffect()

useEffect(() => {

    loadStudents();

}, []);

The function can load API data when the component is first displayed.

21. Complete GET Loading Example

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

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

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

async function loadStudents() {

    setLoading(true);
    setError("");

    try {

        const response = await fetch(
            API_URL
        );

        if (!response.ok) {

            throw new Error(
                "Unable to load students"
            );

        }

        const result =
            await response.json();

        setStudents(
            result.data || []
        );

    } catch (error) {

        setError(
            error.message
        );

    } finally {

        setLoading(false);

    }
}

22. Showing Loading Before Data

if (loading) {

    return (
        <View>

            <ActivityIndicator
                size="large"
            />

            <Text>
                Loading students...
            </Text>

        </View>
    );
}

23. Showing Error Before Data

if (error) {

    return (
        <View>

            <Text>
                {error}
            </Text>

        </View>
    );
}

The UI can then display the data only when loading and error conditions have been handled.

24. Empty Data State

Loading and error are not the only states. The API may successfully return an empty list.

if (
    !loading &&
    !error &&
    students.length === 0
) {

    return (
        <Text>
            No students found.
        </Text>
    );
}

25. Refreshing API Data

Pull-to-refresh can use the same API loading logic.

async function refreshStudents() {

    setLoading(true);

    try {

        await loadStudents();

    } finally {

        setLoading(false);

    }
}

In larger applications, it is often better to keep one function responsible for the loading state rather than setting it in multiple places.

26. Loading State with FlatList

<FlatList
    data={students}
    keyExtractor={item =>
        item.id.toString()
    }
    renderItem={({ item }) => (
        <Text>
            {item.name}
        </Text>
    )}
    ListEmptyComponent={
        loading
            ? <ActivityIndicator
                size="large"
              />
            : <Text>
                No students found.
              </Text>
    }
/>

27. Reusable API Loading Pattern

async function requestData() {

    setLoading(true);
    setError("");

    try {

        const response =
            await fetch(API_URL);

        if (!response.ok) {

            throw new Error(
                "Request failed"
            );

        }

        return await response.json();

    } catch (error) {

        setError(
            error.message
        );

        return null;

    } finally {

        setLoading(false);

    }
}

28. Complete Loading Example

import React, {
    useEffect,
    useState
} from "react";

import {
    View,
    Text,
    ActivityIndicator,
    FlatList
} from "react-native";

const API_URL =
    "https://example.com/api/students.php";

export default function StudentList() {

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

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

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

    async function loadStudents() {

        setLoading(true);
        setError("");

        try {

            const response =
                await fetch(API_URL);

            if (!response.ok) {

                throw new Error(
                    "Unable to load students"
                );

            }

            const result =
                await response.json();

            if (!result.success) {

                throw new Error(
                    result.message ||
                    "API request failed"
                );

            }

            setStudents(
                result.data || []
            );

        } catch (error) {

            setError(
                error.message
            );

        } finally {

            setLoading(false);

        }
    }

    useEffect(() => {

        loadStudents();

    }, []);

    if (loading) {

        return (
            <View>

                <ActivityIndicator
                    size="large"
                />

                <Text>
                    Loading students...
                </Text>

            </View>
        );
    }

    if (error) {

        return (
            <View>

                <Text>
                    {error}
                </Text>

            </View>
        );
    }

    return (
        <FlatList
            data={students}
            keyExtractor={item =>
                item.id.toString()
            }
            renderItem={({ item }) => (
                <Text>
                    {item.name}
                </Text>
            )}
            ListEmptyComponent={
                <Text>
                    No students found.
                </Text>
            }
        />
    );
}

29. Common Loading State Mistakes

  • Forgetting to set loading to true.
  • Forgetting to set loading to false.
  • Not using finally when appropriate.
  • Showing a spinner without handling API errors.
  • Allowing duplicate form submissions.
  • Not displaying an empty state.
  • Showing a loading indicator after the request has finished.
  • Using multiple loading states unnecessarily.
  • Not updating the UI after receiving API data.
  • Ignoring network errors.

30. Complete API State Flow

Screen Opens
      ↓
Loading = true
      ↓
Fetch API
      ↓
   ┌───────────────┐
   ↓               ↓
Success           Error
   ↓               ↓
Loading=false    Loading=false
   ↓               ↓
Show Data       Show Error
   ↓
If no data
   ↓
Show Empty State

A well-designed React Native API screen should clearly handle loading, success, error, and empty-data states.

📌 Key Points

  • Use a loading state while an API request is running.
  • React Native provides ActivityIndicator for loading feedback.
  • Use useState() to manage loading status.
  • Set loading to true before the request.
  • Set loading to false after the request finishes.
  • Using finally is useful for resetting loading after success or failure.
  • Loading states can be used with GET, POST, PUT, and DELETE requests.
  • Use loading to help prevent duplicate submissions.
  • Handle loading, error, success, and empty-data states.
  • FlatList can display loading and empty states.
  • A good API screen should give clear feedback to the user.

🧠 Quick Quiz

Question: Which React Native component is commonly used to display a loading spinner?