To handle asynchronous data loading effectively:
⚙️ 1. The Core Idea
React components often need to fetch data asynchronously — for example, from an API.
This is usually done inside a useEffect hook for functional components (or componentDidMount in class components).
🧠 2. Typical Pattern (Functional Component)
import React, { useEffect, useState } from "react";
import axios from "axios";
function UsersList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchUsers() {
try {
const res = await axios.get("https://jsonplaceholder.typicode.com/users");
setUsers(res.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>
);
}
✅ Explanation:
useEffectruns once when the component mounts.- It calls an async function that fetches data.
loading,error, anddataare managed using state variables.- The UI conditionally renders based on those states.
⚙️ 3. Using fetch Instead of Axios
useEffect(() => {
fetch("https://api.example.com/posts")
.then(res => res.json())
.then(data => setPosts(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, []);
⚙️ 4. Handling Cleanup (to Prevent Memory Leaks)
If your component unmounts before the fetch completes, React might warn about updating an unmounted component.
To handle that:
useEffect(() => {
let isMounted = true;
async function fetchData() {
try {
const res = await axios.get("/api/data");
if (isMounted) setData(res.data);
} finally {
if (isMounted) setLoading(false);
}
}
fetchData();
return () => {
isMounted = false;
};
}, []);
⚙️ 5. Using Suspense (React 18+)
React’s Suspense can simplify async loading when using React.lazy or data fetching libraries like React Query.
Example (with React Query):
import { useQuery } from "@tanstack/react-query";
function Posts() {
const { data, isLoading, error } = useQuery(["posts"], async () => {
const res = await fetch("/api/posts");
return res.json();
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error loading posts</p>;
return (
<ul>
{data.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
);
}
✅ React Query, SWR, Apollo, or Relay handle caching, revalidation, and background refetching efficiently.
⚙️ 6. For Class Components
class Users extends React.Component {
state = { users: [], loading: true, error: null };
async componentDidMount() {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
this.setState({ users: data });
} catch (err) {
this.setState({ error: err.message });
} finally {
this.setState({ loading: false });
}
}
render() {
const { loading, error, users } = this.state;
if (loading) return <p>Loading...</p>;
if (error) return <p>{error}</p>;
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
}
💡 7. Best Practices
| Practice | Why |
|---|---|
✅ Use useEffect for data fetching |
Ensures side effects run at the right time |
✅ Handle loading and error states |
Prevents UI flicker or crashes |
| ✅ Cancel ongoing requests on unmount | Avoids memory leaks |
| ✅ Cache data with React Query / SWR | Improves performance and UX |
| ✅ Show placeholders / skeletons | Provides better perceived performance |
🧱 Summary
In React, asynchronous data loading is handled using
useEffectwith async functions, managingloading,error, anddatastates.
For advanced needs, use libraries like React Query, SWR, or Apollo for caching, pagination, and background refetching.