Skip to content

React Data Fetching: Loading, Error, & Custom Hooks

Posted on:January 22, 2026 at 10:00 AM

Halo, Fellow Coder! 👋

Jadi kamu sudah menguasai useState dan useEffect. Sekarang kamu ingin membuat aplikasi sungguhan. Dan aplikasi sungguhan butuh data nyata. Biasanya, data ini ada di server antah berantah, dan kita perlu mengambilnya.

”Gampang!” katamu. “Pakai fetch aja!”

Ya, betul. Tapi kalau kamu cuma asal tempel fetch di dalam komponen, kamu sedang mengundang masalah. Gimana kalau internet lemot? Gimana kalau server meledak? Gimana kalau user pindah halaman sebelum datanya sampai?

Hari ini, kita akan belajar cara mengambil data (fetch) secara profesional. Bukan cuma asal dapat data, tapi juga menjaga User Experience (Loading & Errors).

Yuk, gas! 🚀

1. Pendekatan Naif (Jangan lakukan di production)

Ini cara kebanyakan pemula memulai:

import { useState, useEffect } from "react";

const UserProfile = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users/1")
      .then((res) => res.json())
      .then((data) => setUser(data));
  }, []);

  if (!user) return <div>...</div>;

  return <h1>{user.name}</h1>;
};

Ini jalan… di WiFi lokal kamu yang ngebut. Tapi apa masalahnya?

  1. Tidak ada Error Handling: Kalau API gagal, aplikasi crash atau layar putih kosong.
  2. Loading State Lemah: Cuma me-return <div>...</div> mengimplikasikan user itu null, padahal mungkin kita cuma lagi nunggu.

2. Pendekatan Robust: 3 State

Untuk membuat komponen yang tahan banting, kita perlu melacak tiga hal:

  1. Data: Hasilnya (kalau sukses).
  2. Loading: Apakah request masih berjalan?
  3. Error: Apakah ada yang salah?

Mari kita tulis ulang.

import { useState, useEffect } from "react";

const UserProfile = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true); // Mulai loading!
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch("https://jsonplaceholder.typicode.com/users/1");

        if (!response.ok) {
          throw new Error("Network response was not ok");
        }

        const result = await response.json();
        setData(result);
        setError(null);
      } catch (err) {
        setError(err.message);
        setData(null);
      } finally {
        setLoading(false); // Sukses atau gagal, stop loading
      }
    };

    fetchData();
  }, []);

  if (loading) return <div>Loading... ⏳</div>;
  if (error) return <div>Error: {error} ⚠️</div>;

  return (
    <div className="card">
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
};

Lihat bedanya? Sekarang user tahu persis apa yang terjadi.

Inilah yang membedakan Junior dan Senior. Menangani “Unhappy Paths” (Kondisi tidak ideal).


3. Level Up: Custom Hook useFetch

Oke, kode di atas bagus. Tapi bayangkan meng-copy 3 state itu (data, loading, error) ke setiap komponen yang butuh data. Ribet. Dan repetitif.

Ingat aturan kita? DRY (Don’t Repeat Yourself).

Mari kita ekstrak logika ini menjadi Custom Hook bernama useFetch.

// hooks/useFetch.js
import { useState, useEffect } from "react";

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

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error(res.statusText);
        const json = await res.json();
        setData(json);
        setError(null);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]); // Jalan ulang kalau URL berubah

  return { data, loading, error };
};

Sekarang lihat betapa bersihnya komponen kita:

import { useFetch } from "./hooks/useFetch";

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

  if (loading) return <div>Loading... ⏳</div>;
  if (error) return <div>Error: {error} ⚠️</div>;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
};

Boom! 💥 Satu baris kode untuk menangani fetching, loading, dan error state. Kamu bisa pakai useFetch hook ini di 100 komponen berbeda.


Penutup

Mengambil data itu kelihatannya simpel, tapi menangani user experience di sekitarnya itu seni.

  1. Selalu tangani Loading state. Jangan biarkan user menebak-nebak.
  2. Selalu tangkap Error. API bisa gagal. Bersiaplah.
  3. Ekstrak logika. Kalau kamu nulis kode yang sama dua kali, jadikan Hook.

Lain kali butuh data, jangan cuma fetch. useFetch.

Happy Coding! 💻☕