Hello! Welcome to the next module in your React journey.
In our last lesson, we built a robust authentication flow with protected routes. However, our login function was just a simulation. In a real application, logging in, fetching user profiles, or loading a list of products all require communication with a backend server over the network. This process is called "working with APIs."
Today, we'll tackle the first and most common task in this area: how to fetch data from a GET endpoint using the fetch or axios API inside a useEffect hook. You'll learn the fundamental pattern for loading external data into your React components, which is a cornerstone for building any dynamic, data-driven application.
The Core Data-Fetching Pattern in React
Before we write any code, let's understand the flow. When you need a component to display data from an API, the process almost always looks like this:
- Set up State: Create a state variable (using
useState) to hold the data once it arrives. It usually starts as an empty array[]ornull. - Trigger the Fetch: Use the
useEffecthook to make the API call. To ensure the request is only made once when the component first renders, you'll provide an empty dependency array ([]). - Make the Request: Inside the effect, use a method like
fetch()or a library likeaxiosto send aGETrequest to the API endpoint. Since this is an asynchronous operation, you'll useasync/awaitor Promise.then()chains. - Update State: Once the data is successfully fetched, update your state variable with the received data. This triggers a re-render.
- Display the Data: Your component now has the data in its state, and you can map over it to render a list or display the information in your JSX.
Implementing Data Fetching with useEffect and fetch
Let's see this pattern in action. We will use the native browser fetch API, which doesn't require any external libraries. For our data source, we'll use JSONPlaceholder, a free fake API perfect for practicing.
The following video provides an excellent walkthrough of this entire process. While the narrator uses TypeScript (which you might see from the interface Post syntax), the core React and JavaScript logic is exactly the same.
Fetching Data in React - Complete Tutorial
Let's watch a section from the "Fetching Data in React - Complete Tutorial" by Cosden Solutions. This clip demonstrates the fundamental pattern of fetching data inside a useEffect hook when a component mounts.
Watch from 00:42 to 06:25. Pay close attention to: How useState is used to create a posts state variable. The structure of the useEffect hook with an empty dependency array ([]). The creation of an async function (fetchPosts) inside the effect. The use of await fetch() to make the request and await response.json() to parse the data. The final call to setPosts() to update the state and trigger a re-render.
A Common Pitfall: useEffect and async Functions
You might have noticed in the video that the async function was defined inside the useEffect hook, not on the effect itself. This is intentional and very important.
You cannot write useEffect(async () => { ... }).
The reason is that useEffect can optionally return a cleanup function. An async function, by its nature, implicitly returns a Promise. React would see this Promise and try to interpret it as a cleanup function, which would lead to an error.
The correct pattern is always to define a separate async function inside the effect and then call it.
This article provides a clear, concise explanation of this rule and reinforces the importance of the empty dependency array to prevent infinite loops.
How to fetch data with React Hooks
Let's read a short section from the article "How to fetch data with React Hooks" by Robin Wieruch. It clarifies why you can't make the effect callback itself async and how to structure your code correctly.
Read the sections from the beginning down to where the empty dependency array [] is introduced. Focus on the code snippets showing the wrong useEffect(async () => ...) approach and the correct way to fix it by creating an inner fetchData function. The text also explains the infinite re-render loop that occurs without the empty array.
Your Turn: Putting It All Together
Let's consolidate what you've learned into a complete, working example. Here is a component that fetches a list of users from JSONPlaceholder and displays their names.
import { useState, useEffect } from 'react';
function UserList() {
// 1. Set up state to hold the users data
const [users, setUsers] = useState([]);
// 2. Use useEffect to fetch data when the component mounts
useEffect(() => {
// 3. Define the async function to perform the fetch
const fetchUsers = async () => {
// Use a try...catch block for basic error handling (we'll improve this later)
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
// 4. Update the state with the fetched data
setUsers(data);
} catch (error) {
console.error("Failed to fetch users:", error);
}
};
// Call the function
fetchUsers();
}, []); // <-- The empty dependency array ensures this runs only once
// 5. Render the data
return (
<div>
<h1>User List</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default UserList;
This code is a template you can adapt for almost any simple data-fetching scenario.
An Alternative: Using axios
While fetch is built-in and works great, many developers prefer a library called axios. It simplifies a few things:
- It automatically transforms the response to JSON, so you don't need the
response.json()step. - The actual data is consistently found in a
dataproperty on the response object. - It has better built-in error handling for network issues.
Let's see a quick comparison.
React Hooks Tutorial - 12 - Fetching data with useEffect Part 1
For a complete picture, let's see how to fetch data using axios. This video from Codevolution walks through the same process but uses axios instead of fetch.
Watch from 00:36 to 05:20. You'll first need to install axios by running npm install axios. As you watch, note the key differences: You import axios at the top. The request is made with axios.get('URL'). The state is updated with response.data. \nThis demonstrates how axios can make the data-fetching code a little more concise.
Choosing between fetch and axios is often a matter of project requirements or team preference. Both are excellent tools to know.
Conclusion
You now have the essential skills to connect your React application to the outside world. This opens the door to building dynamic and interactive user experiences based on real-time data.
Key Takeaways:
- The
useEffectanduseStatehooks are the primary tools for fetching and storing API data in functional components. - To fetch data only once when a component mounts, use
useEffectwith an empty dependency array ([]). - The
useEffectcallback itself cannot beasync. Instead, define anasyncfunction inside the effect and call it. - The native
fetchAPI is a powerful, built-in tool for making network requests, requiring a manual step to parse the JSON response (response.json()). axiosis a popular third-party library that simplifies API calls by automatically handling JSON parsing and providing a more straightforward API.
Next Up:
Our current example works, but it's not very user-friendly. If the network is slow, the user sees a blank screen. If the API request fails, nothing happens. In the next lesson, we will build upon this foundation to manage loading and error states during an API request, providing crucial feedback to the user and making our application more robust.