How to Fetch Data in React: Cheat Sheet + Examples

Fetching data from APIs is a crucial skill for any React developer. Most modern web apps rely heavily on external data and services. Whether you‘re retrieving blog posts from a CMS, product details from an e-commerce backend, or social media feeds from Facebook or Twitter – your React components will often need to request and consume data from remote servers.

In this guide, we‘ll explore the most popular and effective patterns for fetching data in React applications. You‘ll see realistic examples and learn best practices you can apply in your own projects. By the end, you‘ll have a solid understanding of data fetching in React and the confidence to implement it in your apps. Let‘s get started!

Key Concepts

Before we dive into the code, let‘s review some important concepts and terminology:

  • HTTP: Hypertext Transfer Protocol, the communication protocol used to send data between web browsers and servers
  • REST: Representational State Transfer, a popular architectural style for providing standards between computer systems on the web
  • API: Application Programming Interface, a software intermediary that allows two applications to talk to each other
  • JSON: JavaScript Object Notation, a lightweight format for storing and transporting data
  • Promise: An object representing the eventual completion (or failure) of an asynchronous operation and its resulting value

The basic flow goes like this: your React app makes an HTTP request to a REST API endpoint, which returns a promise that resolves to a response. The response data, often in JSON format, can then be stored in the component‘s state or passed down to child components as props.

Fetching Data with the Fetch API

The most basic way to fetch data in React is using the built-in Fetch API. Fetch is promised-based and provides a simple, logical way to fetch resources asynchronously across the network.

Here‘s an example of a component that fetches a list of posts from an API endpoint and displays them:

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

function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(‘https://jsonplaceholder.typicode.com/posts‘)
      .then(response => {
        if (!response.ok) {
          throw new Error(‘Network response was not ok‘);
        }
        return response.json();
      })  
      .then(posts => {
        setPosts(posts);
        setLoading(false);
      })
      .catch(error => {
        console.error(‘Error fetching data: ‘, error);
        setError(error);
        setLoading(false);
      });
  }, []);

  if (loading) return ‘Loading...‘;
  if (error) return ‘Error!‘;

  return (
    <ul>
      {posts.map(post =>
        <li key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </li>
      )}
    </ul>
  );
}

The key steps are:

  1. Initialize state variables to hold the posts, loading state, and any errors
  2. Use the useEffect hook to perform the fetch when the component mounts
  3. Check if the response is ok, if not throw an error
  4. Parse the response as JSON and update the posts state
  5. Catch any errors and update the error and loading state
  6. In the render output, conditionally show a loading message, error message, or the fetched posts

A few things to note:

  • The empty dependency array [] passed to useEffect ensures the fetch only happens once when the component mounts
  • The .ok property on the response object indicates whether the HTTP status code is in the successful range
  • It‘s important to handle errors and provide user feedback for loading and error states

Fetching Data with Axios

While the Fetch API is capable for basic requests, many developers prefer to use a library like Axios for more advanced features and a simpler API. Axios is a promise-based HTTP client that works in both browser and Node.js environments.

Here‘s the same example refactored to use Axios:

import React, { useState, useEffect } from ‘react‘;
import axios from ‘axios‘;

function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    axios.get(‘https://jsonplaceholder.typicode.com/posts‘)
      .then(response => {
        setPosts(response.data);
        setLoading(false);
      })
      .catch(error => {
        console.error(‘Error fetching data: ‘, error);
        setError(error);
        setLoading(false);
      })
  }, []);

  if (loading) return ‘Loading...‘;
  if (error) return ‘Error!‘;

  return (
    <ul>
      {posts.map(post =>
        <li key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </li>  
      )}
    </ul>
  );
}

Axios simplifies a lot of the verbose logic from the Fetch example:

  • No need to check response.ok or parse the JSON, Axios does that automatically and surfaces any errors
  • The response data is available directly on response.data

Axios has many other handy features like request and response interceptors, timeouts, cancellation, and built-in XSRF protection. It can also be used with async/await syntax.

Fetching Data with Async/Await

Async/await is a special syntax to work with promises in a more comfortable fashion, avoiding the need to explicitly configure promise chains. Async functions can contain zero or more await expressions that make JavaScript wait until the promise is settled.

Here‘s our example rewritten once more to use async/await:

import React, { useState, useEffect } from ‘react‘;
import axios from ‘axios‘;

function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await axios.get(‘https://jsonplaceholder.typicode.com/posts‘);
        setPosts(response.data);
        setLoading(false);
      } catch (error) {
        console.error(‘Error fetching data: ‘, error);
        setError(error);
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return ‘Loading...‘;
  if (error) return ‘Error!‘;

  return (
    <ul>
      {posts.map(post =>
        <li key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </li>
      )}
    </ul>
  );  
}

The main changes:

  • Declare an async function called fetchData inside the useEffect callback
  • Use the await keyword when calling axios.get
  • Wrap the Axios call in a try/catch block to properly handle errors
  • Call the fetchData function immediately

Organizing the async operations into a separate function improves readability and avoids errors, as the useEffect callback itself cannot be async.

Creating a Custom Fetch Hook

For better reusability and encapsulation, you can extract the data fetching logic into a custom hook. A hook is a special function that lets you "hook into" React state and lifecycle features from function components.

Here‘s a custom useFetch hook that takes a URL and returns the response data, loading state, and error state:

import { useState, useEffect } from ‘react‘;
import axios from ‘axios‘;

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 axios.get(url);
        setData(response.data);
        setLoading(false);
      } catch (error) {
        console.error(‘Error fetching data: ‘, error);
        setError(error);
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

Now you can use this hook in any component to fetch data with just a few lines of code:

function PostList() {
  const { data: posts, loading, error } = useFetch(‘https://jsonplaceholder.typicode.com/posts‘);

  if (loading) return ‘Loading...‘;
  if (error) return ‘Error!‘;

  return (
    <ul>
      {posts.map(post =>
        <li key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </li>
      )}
    </ul>
  );
}  

The useFetch hook encapsulates all the state logic related to fetching, loading, and error handling. It returns an object with the response data, loading state, and error state. The component can then destructure those values and use them directly.

Note that the url is a dependency of the useEffect hook, so the fetch will rerun whenever the URL changes. This allows the hook to be dynamic based on props or other state.

Fetching Data with React Query

For more complex data fetching scenarios, you may want to reach for a full-featured library like React Query. React Query is a powerful tool for fetching, caching, synchronizing and updating server state in your React applications.

Here‘s how you could fetch the list of posts using React Query:

import React from ‘react‘;
import { useQuery } from ‘react-query‘;
import axios from ‘axios‘;

function PostList() {
  const { data: posts, isLoading, error } = useQuery(‘posts‘, () =>
    axios.get(‘https://jsonplaceholder.typicode.com/posts‘).then(response => response.data)  
  );

  if (isLoading) return ‘Loading...‘;
  if (error) return ‘Error!‘;

  return (
    <ul>
      {posts.map(post => 
        <li key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </li>
      )}
    </ul>
  );
}

React Query manages the request state and caches the response automatically. The useQuery hook returns the data, loading state, and error state, similar to the custom useFetch hook.

The first argument is a unique key for the query, while the second is an async function that returns a promise (in this case the Axios request).

Some benefits of using React Query:

  • Automatic caching and cache invalidation
  • Automatic refetching on window focus or network reconnect
  • Request deduping and request cancellation
  • Pagination and lazy loading
  • Pre-fetching and optimistic updates

If you‘re building an app with complex data requirements, React Query can drastically simplify your state management and improve performance.

Conclusion

We‘ve explored several approaches to fetching data in React, each with their own strengths and use cases:

  • The Fetch API is a simple, native way to make HTTP requests and a good starting point for learning.
  • Axios is a popular, feature-rich library that simplifies a lot of the logic around making requests.
  • Async/await syntax can make promise-based code easier to read and reason about.
  • Custom hooks allow you to encapsulate fetching logic and share it across components.
  • React Query is a powerful tool for managing server state, caching, and updating data in complex applications.

Whichever approach you choose, remember to always handle loading and error states to avoid bad UX. Consider edge cases like slow networks, rate limiting, and user-initiated refetches.

Proper data fetching is a critical skill for building effective React applications. With the knowledge you‘ve gained from this guide, you‘re well-equipped to efficiently fetch data and delight your users with seamless experiences. Happy coding!

Resources

For further reading, check out these resources:

Similar Posts