How to Fetch Data from APIs in React.js - Featured Image
Web development4 min read

How to Fetch Data from APIs in React.js

As a React developer, I've spent countless hours working with APIs to build dynamic web applications. One of the most common tasks you'll encounter is fetching data from external sources and displaying it in your React components. In this guide, I'll walk you through my approach to implementing API calls in React applications - from setting up your project to handling loading states and errors.

Getting started with a new react project

Before diving into the data fetching code, let's make sure we have a React project ready to go. If you already have a project, feel free to skip ahead!

First, ensure Node.js and npm are installed on your system. Then run:

npx create-react-app data-fetching-app
cd data-fetching-app

This creates a new React application with all the necessary configuration handled for you.

Building a data fetching component

For clean code organization, I always prefer creating dedicated components for data fetching operations. Let's create a functional component called DataFetcher.

The first thing we need is state management to track:

  • The fetched data

  • Whether we're still loading

  • Any potential errors

React's useState hook makes this straightforward:

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

const DataFetcher = () => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  // More code coming soon...
};

Making the API request

Now for the core functionality - actually fetching the data! I've found that using async/await with fetch makes for clean, readable code:

const fetchData = async () => {
  try {
    // Replace with your actual API endpoint
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    
    const result = await response.json();
    setData(result);
    setLoading(false);
  } catch (error) {
    console.error("Failed to fetch data:", error);
    setError(error.message);
    setLoading(false);
  }
};

One mistake I made early in my career was not checking if the response was successful with response.ok. This extra validation helps catch HTTP errors (like 404s or 500s) that might otherwise slip through.

Triggering the API call

We want our component to fetch data as soon as it mounts. This is a perfect use case for the useEffect hook:

useEffect(() => {
  fetchData();
}, []); // Empty dependency array means this runs once on mount

The empty dependency array ensures our API call runs only once when the component mounts, not on every re-render.

Displaying the results (and handling states)

Now comes the UI part. We need to handle three possible states:

  1. Loading: While we wait for the API response

  2. Error: If something went wrong

  3. Success: Display the fetched data

Here's a pattern I've found works well:

return (
  <div className="data-container">
    {loading && <p className="loading-message">Loading data...</p>}
    
    {error && (
      <div className="error-message">
        <p>Sorry, something went wrong:</p>
        <p>{error}</p>
      </div>
    )}
    
    {!loading && !error && (
      <ul className="data-list">
        {data.map((item) => (
          <li key={item.id} className="data-item">
            <h3>{item.name}</h3>
            <p>Email: {item.email}</p>
          </li>
        ))}
      </ul>
    )}
  </div>
);

I'm using conditional rendering with the && operator to show the appropriate UI based on our component's state.

Putting It all together

Here's the complete component with everything integrated:

import React, { useEffect, useState } from 'react';
import './DataFetcher.css'; // Don't forget to create a CSS file for styling!

const DataFetcher = () => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      
      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }
      
      const result = await response.json();
      setData(result);
      setLoading(false);
    } catch (error) {
      console.error("Failed to fetch data:", error);
      setError(error.message);
      setLoading(false);
    }
  };

  return (
    <div className="data-container">
      <h2>User Data</h2>
      
      {loading && <p className="loading-message">Loading data...</p>}
      
      {error && (
        <div className="error-message">
          <p>Sorry, something went wrong:</p>
          <p>{error}</p>
        </div>
      )}
      
      {!loading && !error && (
        <ul className="data-list">
          {data.map((item) => (
            <li key={item.id} className="data-item">
              <h3>{item.name}</h3>
              <p>Email: {item.email}</p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default DataFetcher;

Using the component in your app

Finally, import and use your component in App.js:

import React from 'react';
import DataFetcher from './components/DataFetcher';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>React API Data Fetching Demo</h1>
      </header>
      <main>
        <DataFetcher />
      </main>
    </div>
  );
}

export default App;

Advanced considerations

In real-world projects, you might want to consider:

  • Caching: To prevent redundant API calls

  • Pagination: For handling large datasets

  • Custom hooks: To reuse fetching logic across components

  • Abort controllers: To cancel requests when components unmount

  • API libraries: Like Axios or React Query for more advanced functionality

Conclusion

Fetching data from APIs is a fundamental skill for React developers. By following the patterns I've outlined above, you can create components that gracefully handle loading states, errors, and successfully retrieved data.

Remember that good error handling and user feedback are just as important as successfully displaying the data. Your users should always understand what's happening, whether it's waiting for data to load or being informed about an error.

hassaankhan789@gmail.com

Frontend Web Developer

Posted by





Subscribe to our newsletter

Join 2,000+ subscribers

Stay in the loop with everything you need to know.

We care about your data in our privacy policy

Background shadow leftBackground shadow right

Have something to share?

Write on the platform and dummy copy content

Be Part of Something Big

Shifters, a developer-first community platform, is launching soon with all the features. Don't miss out on day one access. Join the waitlist: