DEV Community

Joodi
Joodi

Posted on

2 1 1 2 2

useFetch Hook in React

Image description
Fetching data is a common requirement in React applications. If you frequently make API calls, managing state in multiple components can become repetitive.

In this post, we'll first build a component that fetches data without a custom hook and then refactor it to use a reusable useFetch hook.

Fetching Data Without a Custom Hook

Let's start by creating a React component that fetches data using fetch inside useEffect.

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

const UsersList = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const response = await fetch("https://jsonplaceholder.typicode.com/users");
        if (!response.ok) {
          throw new Error("Failed to fetch users");
        }
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    fetchUsers();
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

export default UsersList;
Enter fullscreen mode Exit fullscreen mode

Issues with this approach:

  • The API logic is tightly coupled to the component.
  • It cannot be reused in other components.
  • Every time you need to fetch data elsewhere, you'll need to rewrite the same logic.

Creating a Custom Hook

Approach 1: Using useState

Let's refactor the fetching logic into a reusable useFetch hook.

import { useState, useEffect } from "react";

const 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;
Enter fullscreen mode Exit fullscreen mode

Advantages

  • It centralizes API fetching logic.
  • It can be reused across multiple components.
  • It simplifies components by removing API call handling.

Approach 2: Using useReducer

Another way to structure the useFetch hook is by using useReducer instead of useState. This approach makes state management more structured.

import { useReducer, useEffect } from "react";

const initialState = {
  data: null,
  loading: true,
  error: null,
};

const fetchReducer = (state, action) => {
  switch (action.type) {
    case "FETCH_SUCCESS":
      return { data: action.payload, loading: false, error: null };
    case "FETCH_ERROR":
      return { data: null, loading: false, error: action.payload };
    default:
      return state;
  }
};

const useFetch = (url) => {
  const [state, dispatch] = useReducer(fetchReducer, initialState);

  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();
        dispatch({ type: "FETCH_SUCCESS", payload: result });
      } catch (err) {
        dispatch({ type: "FETCH_ERROR", payload: err.message });
      }
    };
    fetchData();
  }, [url]);

  return state;
};

export default useFetch;
Enter fullscreen mode Exit fullscreen mode

Using the Custom Hook in a Component

Now, our UsersList component is much cleaner:

import React from "react";
import useFetch from "./useFetch";

const UsersList = () => {
  const { data: users, loading, error } = useFetch("https://jsonplaceholder.typicode.com/users");

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

export default UsersList;
Enter fullscreen mode Exit fullscreen mode

By using useFetch, we have separated the data fetching logic from our component, making it more modular and reusable.

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

AWS Q Developer image

Your AI Code Assistant

Generate and update README files, create data-flow diagrams, and keep your project fully documented. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay