Introduction
When a React application fetches data from an API, users may need to wait while the request is being processed. Sometimes the request can also fail because of network problems, server errors, or invalid responses. Loading and Error Handling helps us show useful feedback during these situations. In this chapter, we will practice how to manage loading states, display error messages, retry requests, and handle API failures in React. React js Loading and Error Handling practice questions with solutions help to build concepts.
1. How do you create a Loading State in React?
You can create a loading state using useState().
Solution:
import { useState } from "react";
function App() {
const [loading, setLoading] = useState(true);
return (
<div>
{loading ? <p>Loading...</p> : <p>Data Loaded</p>}
</div>
);
}
export default App;
Here:
loadingstores the current loading status.setLoading()updates the status.- When
loadingistrue, the loading message is displayed. - When it becomes
false, the loaded content is displayed.
2. How do you show a Loading Message while Fetching API Data?
You can set the loading state to true before starting the request and set it to false after the request finishes.
Solution:
import { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
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);
} finally {
setLoading(false);
}
}
fetchUsers();
}, []);
if (loading) {
return <p>Loading users...</p>;
}
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default Users;
The finally block makes sure that the loading state is changed to false whether the request succeeds or fails.
3. How do you create an Error State in React?
You can create an error state using useState().
Solution:
import { useState } from "react";
function App() {
const [error, setError] = useState("");
return (
<div>
{error && <p>{error}</p>}
</div>
);
}
export default App;
When an error occurs, you can update the state:
setError("Something went wrong.");
React will then display the error message.
4. How do you Handle Loading and Error States together?
A common approach is to use separate state variables for data, loading, and error.
Solution:
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 {
setLoading(true);
setError("");
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);
} finally {
setLoading(false);
}
}
fetchUsers();
}, []);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error}</p>;
}
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default Users;
The component now handles three important states:
- Loading
- Error
- Success
This pattern is commonly used when working with APIs.
5. How do you Handle API Errors using try…catch?
You can use try...catch to handle errors that occur while performing an asynchronous operation.
Solution:
import { useEffect, useState } from "react";
function Products() {
const [products, setProducts] = useState([]);
const [error, setError] = useState("");
useEffect(() => {
async function fetchProducts() {
try {
const response = await fetch(
"https://example.com/api/products"
);
if (!response.ok) {
throw new Error("Unable to load products");
}
const data = await response.json();
setProducts(data);
} catch (err) {
setError(err.message);
}
}
fetchProducts();
}, []);
if (error) {
return <p>{error}</p>;
}
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
export default Products;
The catch block receives an error when the asynchronous operation fails.
It is also important to check response.ok because fetch() does not automatically reject a Promise for HTTP responses such as 404 or 500.
6. How do you Display Different UI for Loading, Error, and Success?
You can use conditional rendering to display different content for each state.
Solution:
function UserStatus({ loading, error, users }) {
if (loading) {
return <p>Loading users...</p>;
}
if (error) {
return <p>Failed to load users.</p>;
}
if (users.length === 0) {
return <p>No users found.</p>;
}
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default UserStatus;
This example handles four possible UI states:
- Loading
- Error
- Empty result
- Successful result
Handling these states makes an application easier for users to understand.
7. How do you Add a Retry Button after an API Error?
A retry button can call the same function again when a request fails.
Solution:
import { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
async function fetchUsers() {
try {
setLoading(true);
setError("");
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
if (!response.ok) {
throw new Error("Failed to load users");
}
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
useEffect(() => {
fetchUsers();
}, []);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return (
<div>
<p>{error}</p>
<button onClick={fetchUsers}>Retry</button>
</div>
);
}
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default Users;
The Retry button calls fetchUsers() again.
This is useful for temporary network or server problems.
8. How do you Handle an Empty API Response?
A successful API request does not always mean that useful data was found.
For example, an API may return an empty array:
[]
You can check the length of the array before rendering the list.
Solution:
function ProductList({ products }) {
if (products.length === 0) {
return <p>No products found.</p>;
}
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
export default ProductList;
This provides a better user experience than showing a completely empty page.
It is useful to distinguish between:
- Loading
- Error
- Empty result
- Successful data
9. How do you Handle Loading and Error States when a Value Changes?
Sometimes an API request depends on a changing value such as a user ID.
When the ID changes, the component can fetch new data and reset its loading and error states.
Solution:
import { useEffect, useState } from "react";
function User({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
async function fetchUser() {
try {
setLoading(true);
setError("");
const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
if (!response.ok) {
throw new Error("User not found");
}
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
setUser(null);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
if (loading) {
return <p>Loading user...</p>;
}
if (error) {
return <p>Error: {error}</p>;
}
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
export default User;
The dependency:
[userId]
causes the effect to synchronize again when userId changes.
The component also resets the error before starting a new request.
10. How do you Build a Practical React API Component with Loading and Error Handling?
Create a component that fetches users and displays loading, error, empty, and success states.
Solution:
import { useEffect, useState } from "react";
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
async function fetchUsers() {
try {
setLoading(true);
setError("");
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);
} finally {
setLoading(false);
}
}
useEffect(() => {
fetchUsers();
}, []);
if (loading) {
return <p>Loading users...</p>;
}
if (error) {
return (
<div>
<p>Error: {error}</p>
<button onClick={fetchUsers}>Try Again</button>
</div>
);
}
if (users.length === 0) {
return <p>No users found.</p>;
}
return (
<div>
<h2>User List</h2>
{users.map((user) => (
<div key={user.id}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
);
}
export default UserList;
What this example handles:
loading→ Shows loading feedback.error→ Shows an error message.users.length === 0→ Handles an empty response.- Successful response → Displays the user list.
Try Again→ Allows the user to retry the request.response.ok→ Detects unsuccessful HTTP responses.
This is a practical pattern that can be reused in many React applications.
Key Takeaways
- Loading states tell users that an operation is in progress.
- Error states provide feedback when an operation fails.
useState()can manage loading, error, and data states.- Use
try...catchfor asynchronous error handling. - Check
response.okwhen usingfetch(). finallyis useful for ending the loading state after success or failure.- Handle empty API results separately from errors.
- A retry button can allow users to repeat a failed request.
- When dependencies change, reset and manage loading/error states for the new request.
- Good loading and error handling makes React applications more reliable and user-friendly.
FAQs
1. What is Loading State in React?
Loading state indicates that an asynchronous operation, such as an API request, is currently in progress.
2. What is Error Handling in React?
Error handling means detecting and responding to problems that occur during operations such as API requests or other asynchronous tasks.
3. Why are Loading and Error States important in React?
They provide feedback to users while data is loading and when something goes wrong, instead of leaving the interface blank or confusing.
4. Can useState() be used for Loading and Error Handling?
Yes. useState() can store values such as loading, error, and fetched data.
5. Does fetch() automatically throw an error for a 404 or 500 response?
No. fetch() normally resolves with a Response object even for HTTP error status codes. You should check response.ok and throw an error when appropriate.
6. Why is finally used while fetching data?
The finally block runs after the try or catch work completes, making it useful for setting loading to false regardless of whether the request succeeds or fails.
7. Should React applications handle empty API results?
Yes. An empty result is different from an API error. Showing a message such as No products found gives users clearer feedback.
Written by Shubhranshu Shekhar, who has trained 20000+ students in coding.
