Hello Sunil
fetching-data-with-fetch-api-react-feature-image

Fetching and Consuming Data with Fetch API in React

In modern web applications, interacting with APIs is essential. APIs allow you to fetch, add, update, and delete data on servers, making your application dynamic and interactive.

In this blog, we’ll cover how to use the Fetch API in React to perform these actions with ease. We’ll look at making GET, POST, PUT, and DELETE requests, handling errors, and using async-await syntax for better readability.

Also Read: How To Use Axios With React: The Definitive Guide

For this tutorial, we’ll use the JSONPlaceholder API, which provides sample data for testing and learning purposes.

The GET request is used to retrieve data from an API. In React, we can use the fetch() function to make this request. Let’s create a function that fetches a list of posts.

GetRequest.jsx

import { useState, useEffect } from 'react';


const GetRequest = () => {
	const [posts, setPosts] = useState([]);

	useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then(response => response.json())  // Parse JSON from response
      .then(data => setPosts(data))       // Update state with data
      .catch(error => console.error("Error fetching posts:", error));
  }, []);



  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default GetRequest;

Explanation

fetch(): Initiates the GET request to the API.
.then(response => response.json()): Converts the response to JSON format.
.then(data => setPosts(data)): Stores the data in the state variable posts.
.catch(error => console.error(…)): Catches and logs any errors that occur.

Output:

fetch-api-react-get-request

How to Make a POST Request

POST requests are used to send new data to the server. Here, we’ll add a new post. For POST, we also need to include headers and a body containing the new data.

This example component will fetch a list of posts on load and allow you to add a new post by clicking a button.

PostRequest.jsx

import { useState, useEffect } from 'react';

const PostRequest = () => {
	const [posts, setPosts] = useState([]);
  const [newPostTitle, setNewPostTitle] = useState('');
  const [newPostBody, setNewPostBody] = useState('');

	// Fetch posts when the component mounts
	useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then(response => response.json())
      .then(data => setPosts(data))
      .catch(error => console.error("Error fetching posts:", error));
  }, []);

  // Function to handle adding a new post
  const handleAddPost = () => {
    fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: newPostTitle,
        body: newPostBody,
        userId: 1,
      }),
    })
      .then(response => response.json())
      .then(newPost => {
        setPosts([...posts, newPost]);
        setNewPostTitle(''); // Clear the input fields
        setNewPostBody('');
      })
      .catch(error => console.error("Error adding post:", error));
  };
	return (
		<div>
		<h1>Posts</h1>
		<ul>
			{posts.map(post => (
				<li key={post.id}>
					<h2>{post.title}</h2>
					<p>{post.body}</p>
				</li>
			))}
		</ul>

		<h2>Add New Post</h2>
		<input
			type="text"
			placeholder="Post Title"
			value={newPostTitle}
			onChange={(e) => setNewPostTitle(e.target.value)}
		/>
		<textarea
			placeholder="Post Body"
			value={newPostBody}
			onChange={(e) => setNewPostBody(e.target.value)}
		/>
		<button onClick={handleAddPost}>Add Post</button>
	</div>
	)
}

export default PostRequest

Explanation

  • useState Hooks: Used to manage state for posts, the new post title, and the new post body.
  • useEffect: Fetches posts when the component mounts and stores them in the posts state.
  • handleAddPost Function: Sends a POST request to add a new post.
    • method: "POST": Specifies the HTTP method.
    • headers: Sets Content-Type to "application/json" to let the API know we’re sending JSON data.
    • body: Contains the data we want to send in JSON format.
    • .then(newPost => setPosts([...posts, newPost])): Adds the newly created post to the current list of posts.
  • Form Inputs: Allows users to enter a new post title and body.

This example shows how to combine fetching and adding new posts in a React component, making it easy to extend for further CRUD operations.

Output:

fetch-api-react-post-request

PUT requests update an existing resource. Here’s a complete example in React that shows how to use a PUT request to update an existing post using fetch() and JSON.stringify().

This example includes fetching posts, selecting a post to edit, and updating the post by sending a PUT request.

PutRequest.jsx

import { useState, useEffect } from 'react';

const PutRequest = () => {
	const [posts, setPosts] = useState([]);
  const [editPostId, setEditPostId] = useState(null);
  const [editTitle, setEditTitle] = useState('');
  const [editBody, setEditBody] = useState('');

	// Fetch posts when the component mounts
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then(response => response.json())
      .then(data => setPosts(data))
      .catch(error => console.error("Error fetching posts:", error));
  }, []);

  // Function to handle selecting a post for editing
  const handleEdit = (post) => {
    setEditPostId(post.id);
    setEditTitle(post.title);
    setEditBody(post.body);
  };

  // Function to handle updating a post
  const handleUpdatePost = () => {
    fetch(`https://jsonplaceholder.typicode.com/posts/${editPostId}`, {
      method: "PUT",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        id: editPostId,
        title: editTitle,
        body: editBody,
        userId: 1,
      }),
    })
      .then(response => response.json())
      .then(updatedPost => {
        // Update the post in the local state
        setPosts(posts.map(post =>
          post.id === editPostId ? updatedPost : post
        ));
        // Clear edit fields
        setEditPostId(null);
        setEditTitle('');
        setEditBody('');
      })
      .catch(error => console.error("Error updating post:", error));
  };


	return (
		<div>
      <h1>Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.body}</p>
            <button onClick={() => handleEdit(post)}>Edit</button>
          </li>
        ))}
      </ul>

      {editPostId && (
        <div>
          <h2>Edit Post</h2>
          <input
            type="text"
            value={editTitle}
            onChange={(e) => setEditTitle(e.target.value)}
            placeholder="Edit Title"
          />
          <textarea
            value={editBody}
            onChange={(e) => setEditBody(e.target.value)}
            placeholder="Edit Body"
          />
          <button onClick={handleUpdatePost}>Update Post</button>
        </div>
      )}
    </div>
	)
}

export default PutRequest

Explanation

  1. State Variables:
    • posts: Holds the list of posts.
    • editPostId, editTitle, and editBody: Used to manage the currently selected post for editing.
  2. useEffect Hook:
    • Fetches the posts from the API on component mount.
  3. handleEdit Function:
    • Sets the selected post’s ID, title, and body to allow editing.
  4. handleUpdatePost Function:
    • Sends a PUT request to update the selected post.
    • Uses JSON.stringify() to convert the updated data to a JSON string.
    • After receiving the updated post from the server, it updates the post in the posts array.
    • Clears the edit fields after updating.
  5. Edit Form:
    • Appears when a post is selected for editing.
    • Includes an input for the post title and a textarea for the post body.

Output:

fetch-api-react-put-request

DELETE requests remove an item from the server. Here’s a full example of a React component that includes fetching, adding, and deleting posts using fetch() with JSONPlaceholder.

This example demonstrates how to make a DELETE request to remove a post.

DeleteRequest.jsx

import { useState, useEffect } from 'react';

const DeleteRequest = () => {
	const [posts, setPosts] = useState([]);
  const [newPostTitle, setNewPostTitle] = useState('');
  const [newPostBody, setNewPostBody] = useState('');

	 // Fetch posts when the component mounts
	 useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then(response => response.json())
      .then(data => setPosts(data))
      .catch(error => console.error("Error fetching posts:", error));
  }, []);

  // Function to handle adding a new post
  const handleAddPost = () => {
    fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: newPostTitle,
        body: newPostBody,
        userId: 1,
      }),
    })
      .then(response => response.json())
      .then(newPost => {
        setPosts([...posts, newPost]);
        setNewPostTitle(''); // Clear the input fields
        setNewPostBody('');
      })
      .catch(error => console.error("Error adding post:", error));
  };

  // Function to handle deleting a post
  const handleDeletePost = (postId) => {
    fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
      method: "DELETE",
    })
      .then(response => {
        if (response.ok) {
          setPosts(posts.filter(post => post.id !== postId));
        } else {
          console.error("Error deleting post:", response.statusText);
        }
      })
      .catch(error => console.error("Error deleting post:", error));
  };
	return (
		<div>
      <h1>Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.body}</p>
            <button onClick={() => handleDeletePost(post.id)}>Delete</button>
          </li>
        ))}
      </ul>

      <h2>Add New Post</h2>
      <input
        type="text"
        placeholder="Post Title"
        value={newPostTitle}
        onChange={(e) => setNewPostTitle(e.target.value)}
      />
      <textarea
        placeholder="Post Body"
        value={newPostBody}
        onChange={(e) => setNewPostBody(e.target.value)}
      />
      <button onClick={handleAddPost}>Add Post</button>
    </div>
	)
}

export default DeleteRequest

Explanation

  1. State Variables:
    • posts: Holds the list of posts.
    • newPostTitle and newPostBody: Used to store input values for a new post.
  2. useEffect Hook:
    • Fetches posts from the API when the component mounts.
  3. handleAddPost Function:
    • Sends a POST request to add a new post to the API.
    • Uses JSON.stringify() to convert the new post data into JSON format for the request.
    • Updates the posts state with the new post.
  4. handleDeletePost Function:
    • Sends a DELETE request to remove a post with the specified postId.
    • If the response is successful (response.ok), it filters out the deleted post from the posts state.
  5. UI Elements:
    • Displays a list of posts, each with a “Delete” button to remove it.
    • Provides input fields and a button for adding a new post.

Output:

fetch-api-react-delete-request

Handling errors helps ensure a smooth user experience. You can handle errors by adding a .catch() block to each fetch call.

Here’s a complete React example demonstrating how to handle errors effectively when using the Fetch API. In this example, we will add error handling for both fetching and adding posts. If an error occurs, it will display an error message to the user.

HandleErrors.jsx

import { useState, useEffect } from 'react';

const HandleErrors = () => {
	const [posts, setPosts] = useState([]);
  const [newPostTitle, setNewPostTitle] = useState('');
  const [newPostBody, setNewPostBody] = useState('');
  const [error, setError] = useState(null);

  // Fetch posts with error handling
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then(response => {
        if (!response.ok) {
          throw new Error(`Error fetching posts: ${response.statusText}`);
        }
        return response.json();
      })
      .then(data => setPosts(data))
      .catch(error => setError(error.message));
  }, []);

  // Function to handle adding a new post with error handling
  const handleAddPost = () => {
    fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: newPostTitle,
        body: newPostBody,
        userId: 1,
      }),
    })
      .then(response => {
        if (!response.ok) {
          throw new Error(`Error adding post: ${response.statusText}`);
        }
        return response.json();
      })
      .then(newPost => {
        setPosts([...posts, newPost]);
        setNewPostTitle(''); // Clear the input fields
        setNewPostBody('');
        setError(null); // Clear any existing error
      })
      .catch(error => setError(error.message));
  };
	return (
		<div>
		<h1>Posts</h1>
		{/* Display error message if any */}
		{error && <p style={{ color: 'red' }}>{error}</p>}

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

		<h2>Add New Post</h2>
		<input
			type="text"
			placeholder="Post Title"
			value={newPostTitle}
			onChange={(e) => setNewPostTitle(e.target.value)}
		/>
		<textarea
			placeholder="Post Body"
			value={newPostBody}
			onChange={(e) => setNewPostBody(e.target.value)}
		/>
		<button onClick={handleAddPost}>Add Post</button>
	</div>
	)
}

export default HandleErrors

Explanation of Error Handling

  1. Error State:
    • The error state variable is used to store any error messages that occur. This way, we can display error messages directly in the UI.
  2. Error Handling in Fetch Request (GET):
    • After the fetch() call, we check if the response is OK with response.ok. If not, we throw an error with a custom message that includes response.statusText.
    • This error is then caught in the .catch() block, where we set the error message to be displayed.
  3. Error Handling in POST Request:
    • Similarly, we check if the response for the POST request is OK. If it’s not, we throw an error with a descriptive message.
    • The .catch() block will set the error message to be shown to the user if there is an issue with adding the new post.
  4. Displaying Error Messages:
    • We conditionally render the error message in the UI using {error && <p style={{ color: 'red' }}>{error}</p>}. If an error occurs, it will appear in red text at the top of the component.

Output:

fetch-api-react-error-handle-request

The async-await syntax makes the code more readable, especially when chaining multiple asynchronous calls.

Here’s a full example of a React component that demonstrates using async-await syntax with the Fetch API. This example includes fetching posts, adding a new post, and handling errors with try-catch blocks to make the code cleaner and easier to read.

AsyncAwait.jsx

import { useState, useEffect } from 'react';

const AsyncAwait = () => {
	const [posts, setPosts] = useState([]);
  const [newPostTitle, setNewPostTitle] = useState('');
  const [newPostBody, setNewPostBody] = useState('');
  const [error, setError] = useState(null);

  // Fetch posts using async-await and error handling
  useEffect(() => {
    const fetchPosts = async () => {
      try {
        const response = await fetch("https://jsonplaceholder.typicode.com/posts");
        if (!response.ok) {
          throw new Error(`Error fetching posts: ${response.statusText}`);
        }
        const data = await response.json();
        setPosts(data);
      } catch (error) {
        setError(error.message);
      }
    };

    fetchPosts();
  }, []);

  // Function to handle adding a new post with async-await and error handling
  const handleAddPost = async () => {
    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          title: newPostTitle,
          body: newPostBody,
          userId: 1,
        }),
      });
      if (!response.ok) {
        throw new Error(`Error adding post: ${response.statusText}`);
      }
      const newPost = await response.json();
      setPosts([...posts, newPost]);
      setNewPostTitle(''); // Clear the input fields
      setNewPostBody('');
      setError(null); // Clear any existing error
    } catch (error) {
      setError(error.message);
    }
  };
	return (
		<div>
      <h1>Posts</h1>
      {/* Display error message if any */}
      {error && <p style={{ color: 'red' }}>{error}</p>}

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

      <h2>Add New Post</h2>
      <input
        type="text"
        placeholder="Post Title"
        value={newPostTitle}
        onChange={(e) => setNewPostTitle(e.target.value)}
      />
      <textarea
        placeholder="Post Body"
        value={newPostBody}
        onChange={(e) => setNewPostBody(e.target.value)}
      />
      <button onClick={handleAddPost}>Add Post</button>
    </div>
	)
}

export default AsyncAwait

Explanation of Async-Await Usage

  1. Fetching Posts with Async-Await:
    • Inside useEffect, we define an async function called fetchPosts.
    • await fetch("https://jsonplaceholder.typicode.com/posts") waits for the response.
    • If the response isn’t OK (!response.ok), it throws an error, which is caught in the catch block.
    • The await response.json() parses the data, which is then set to the posts state.
  2. Adding a New Post with Async-Await:
    • The handleAddPost function is an async function that makes a POST request to add a new post.
    • await fetch(...) sends the POST request, and if it’s successful, the new post data is added to the state.
    • If there’s an error, it’s caught in the catch block, and the error message is shown in the UI.
  3. Error Display:
    • The error message is stored in error state and displayed if present.

Using the Fetch API in React is straightforward and powerful for interacting with external APIs.

You’ve learned how to perform GET, POST, PUT, and DELETE requests, handle errors, and use async-await syntax to write clean, readable code. Now, you can confidently consume and manipulate data in your React applications.

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

Similar articles you may like

Sunil Pradhan

Hi there đź‘‹ I am a front-end developer passionate about cutting-edge, semantic, pixel-perfect design. Writing helps me to understand things better.

Add comment

Stay Updated

Want to be notified when our article is published? Enter your email address below to be the first to know.

Sunil Pradhan

Hi there đź‘‹ I am a front-end developer passionate about cutting-edge, semantic, pixel-perfect design. Writing helps me to understand things better.