When I first began working with React functional components, one of the most common tasks I faced was fetching data from APIs. Whether it was pulling product data for an eCommerce dashboard or retrieving user information for a client portal, handling fetch results efficiently was crucial.
Over the years, I’ve refined my approach to fetching data in React. In this tutorial, I’ll show you simple, modern, and reliable ways to get fetch results using React hooks like useState and useEffect.
We’ll go through multiple methods, each with complete code examples and concise explanations. By the end of this guide, you’ll know exactly how to fetch data, handle loading and error states, and render results cleanly in your React app.
Method 1 – Fetch Data Using useEffect and useState
The most common way to fetch data in React functional components is by combining useEffect (for side effects) with useState (for managing data).
Here’s a simple example where I fetch random user data from the Random User API.
import React, { useEffect, useState } from "react";
function RandomUser() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://randomuser.me/api/")
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => {
setUser(data.results[0]);
setLoading(false);
})
.catch((error) => {
setError(error.message);
setLoading(false);
});
}, []);
if (loading) return <p>Loading user data...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>User Information</h2>
<p>
Name: {user.name.first} {user.name.last}
</p>
<p>Email: {user.email}</p>
<img src={user.picture.large} alt="User" />
</div>
);
}
export default RandomUser;You can refer to the screenshot below to see the output.

In this method, I use useEffect to trigger the fetch operation when the component mounts. The useState hooks manage the user data, loading status, and error state. Once the data is fetched, the component re-renders to display the result.
Method 2 – Fetch Data Using async/await Inside useEffect
While the .then() syntax works fine, I personally prefer using async/await because it makes the code cleaner and easier to read.
Here’s how you can rewrite the same example using async/await:
import React, { useEffect, useState } from "react";
function RandomUserAsync() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
const response = await fetch("https://randomuser.me/api/");
if (!response.ok) {
throw new Error("Failed to fetch user data");
}
const data = await response.json();
setUser(data.results[0]);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUser();
}, []);
if (loading) return <p>Loading user data...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>Random User (Async/Await)</h2>
<p>
Name: {user.name.first} {user.name.last}
</p>
<p>Email: {user.email}</p>
<img src={user.picture.large} alt="User" />
</div>
);
}
export default RandomUserAsync;You can refer to the screenshot below to see the output.

The async/await syntax makes the asynchronous code look synchronous, improving readability. Using try…catch…finally ensures proper error handling and cleanup once the data is fetched.
Method 3 – Fetch Data When Dependencies Change
Sometimes, you need to refetch data when a dependency changes, for example, when a user selects a different city to view weather data.
Here’s how I handle that scenario:
import React, { useEffect, useState } from "react";
function WeatherData() {
const [city, setCity] = useState("New York");
const [weather, setWeather] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchWeather = async () => {
setLoading(true);
const response = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=40.71&longitude=-74.01¤t_weather=true`
);
const data = await response.json();
setWeather(data.current_weather);
setLoading(false);
};
fetchWeather();
}, [city]);
return (
<div>
<h2>Weather Data for {city}</h2>
<select onChange={(e) => setCity(e.target.value)}>
<option>New York</option>
<option>Los Angeles</option>
<option>Chicago</option>
</select>
{loading && <p>Loading...</p>}
{weather && (
<div>
<p>Temperature: {weather.temperature}°C</p>
<p>Wind Speed: {weather.windspeed} km/h</p>
</div>
)}
</div>
);
}
export default WeatherData;You can refer to the screenshot below to see the output.

By adding the city as a dependency in useEffect, React automatically refetches data whenever the city value changes. This approach is perfect for dynamic dashboards or search-based applications.
Method 4 – Use a Custom Hook for Fetching Data
As your React app grows, you’ll likely need to fetch data in multiple components. Instead of repeating the same logic everywhere, I prefer creating a custom hook for reusability.
Here’s an example of a reusable useFetch hook:
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch data");
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;Now you can use this hook in any component:
import React from "react";
import useFetch from "./useFetch";
function UsersList() {
const { data, loading, error } = useFetch("https://jsonplaceholder.typicode.com/users");
if (loading) return <p>Loading users...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>User List</h2>
<ul>
{data.map((user) => (
<li key={user.id}>
{user.name} – {user.email}
</li>
))}
</ul>
</div>
);
}
export default UsersList;This method keeps your components clean and focused. The useFetch hook encapsulates all the fetching logic, making it reusable for any API endpoint.
Bonus Tip – Handle Abort with Cleanup
When you fetch data in React, it’s good practice to abort ongoing requests if the component unmounts before the fetch completes.
Here’s how you can do that:
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const response = await fetch(url, { signal: controller.signal });
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== "AbortError") setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
return () => controller.abort();
}, [url]);Using AbortController helps prevent memory leaks and ensures your app doesn’t try to update state after a component is unmounted.
Best Practices When Fetching Data in React
From my experience, here are a few golden rules to follow:
- Always handle loading and error states.
- Use async/await for cleaner asynchronous code.
- Create reusable hooks for consistency.
- Add cleanup functions to avoid memory leaks.
- Keep API URLs and keys in environment variables.
When I started building data-driven React apps, fetching data felt repetitive and error-prone. But once I began structuring my fetch logic into hooks and following best practices, my code became cleaner, faster, and easier to debug.
Fetching data is one of the most common tasks in React development, and mastering it early will save you countless hours down the road.
You can also read:
- React Component Testing Best Practices
- Convert SVG to React Component
- React Function Components with TypeScript
- How to Use React Frame Component

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.