Logo Catatan Kader Logo Catatan Kader
  • Beranda
Beranda
Modul 2 • Data Fetching

Integrasi API: Axios, onMounted, & Rendering Data

Data Fetching Network
Vue 3 Axios Composition API

"Menghubungkan Antarmuka dengan Realitas Data di Server"

Estimasi Belajar: 45 Menit
Target: Membangun Grid Data Dinamis

1 Kenapa Menggunakan Axios?

Aplikasi Frontend modern tidak berdiri sendiri; mereka membutuhkan data dari server (Backend) untuk ditampilkan kepada pengguna. Untuk melakukan komunikasi ini, kita melakukan apa yang disebut HTTP Request (GET, POST, PUT, DELETE).

Secara bawaan, JavaScript modern memiliki fitur bernama fetch(). Namun, komunitas Vue.js (dan React/Angular) sangat sering menggunakan pustaka pihak ketiga bernama Axios. Mengapa?

Transformasi JSON Otomatis

Jika menggunakan fetch, kita harus menulis response.json(). Axios melakukan ini secara otomatis di balik layar.

Error Handling Lebih Baik

Axios otomatis melempar error (me-reject promise) jika status code bukan 2xx (misal 404 atau 500). Fetch tidak melakukan ini.

Interceptors & Security

Sangat mudah menambahkan token otorisasi (JWT) ke setiap request secara otomatis menggunakan fitur Interceptors.

Request Cancellation

Axios memungkinkan kita membatalkan request yang sedang berjalan (sangat berguna untuk fitur pencarian / auto-complete).

Untuk menginstal Axios di proyek Vue Anda, cukup jalankan perintah berikut di terminal:

npm install axios

2 Lifecycle: Kapan Harus Fetch Data?

Pertanyaan terbesar pemula adalah: "Di mana saya harus meletakkan kode untuk mengambil data API?". Jawabannya adalah di dalam Hook onMounted.

onMounted adalah sebuah fungsi dari Vue yang menjamin bahwa komponen Anda telah selesai dibuat dan dimasukkan ke dalam DOM browser. Ini adalah momen paling aman untuk memanggil API eksternal.

Konsep 3 State (Tiga Keadaan)

Dalam melakukan HTTP Request, seorang Kader Engineer yang baik harus selalu menyiapkan 3 variabel state reaktif:

  1. Data State: Untuk menyimpan hasil (response) dari API. Biasanya berupa Array kosong ref([]).
  2. Loading State: Bernilai boolean ref(false). Diubah menjadi true saat request berjalan, dan kembali false saat selesai.
  3. Error State: Untuk menyimpan pesan error jika API gagal diakses ref(null).

Mari kita lihat struktur dasar kodenya:

<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';

// 1. Deklarasi 3 State Utama
const users = ref([]);      // Menyimpan daftar pengguna
const isLoading = ref(false); // Indikator loading UI
const errorMessage = ref(null); // Penampung pesan error

// 2. Buat fungsi Async untuk memanggil API
const fetchUsers = async () => {
  isLoading.value = true; // Mulai loading
  errorMessage.value = null; // Reset error sebelumnya

  try {
    // Menggunakan Axios GET Request
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    
    // Axios menyimpan data asli di dalam properti .data
    users.value = response.data; 
    
  } catch (error) {
    // Menangkap error (misal internet putus atau server mati)
    console.error("Gagal mengambil data:", error);
    errorMessage.value = "Maaf, terjadi kesalahan saat memuat data.";
  } finally {
    // Blok ini selalu dijalankan baik sukses maupun error
    isLoading.value = false; // Matikan loading
  }
};

// 3. Panggil fungsi di onMounted
onMounted(() => {
  fetchUsers();
});
</script>

3 Rendering List dengan v-for

Setelah data berhasil diambil dan dimasukkan ke dalam variabel users, langkah selanjutnya adalah menampilkannya di HTML. Vue memiliki direktif khusus yang sangat powerful bernama v-for.

Cara kerja v-for sangat mirip dengan perulangan for...of di JavaScript. Anda menggunakan sintaks v-for="item in items".

Pentingnya Atribut :key

Ketika menggunakan v-for, Anda wajib menyertakan atribut :key (shorthand dari v-bind:key). Key ini harus berupa nilai unik (biasanya ID dari database).

Mengapa ini wajib? Vue menggunakan Virtual DOM. Jika urutan data berubah (misal karena fitur *sorting* atau *filtering*), Vue butuh penanda unik (key) untuk melacak elemen HTML mana yang harus dipindah, dihapus, atau dipertahankan tanpa harus merender ulang seluruh daftar dari awal. Ini membuat performa aplikasi Anda sangat cepat.

<template>
  <div class="container">
    <h2>Daftar Pengguna</h2>

    <!-- 1. Tampilkan Loading State -->
    <div v-if="isLoading" class="loading-spinner">
      Memuat data dari server...
    </div>

    <!-- 2. Tampilkan Error State -->
    <div v-else-if="errorMessage" class="error-alert">
      {{ errorMessage }}
      <button @click="fetchUsers">Coba Lagi</button>
    </div>

    <!-- 3. Tampilkan Data State dengan v-for -->
    <ul v-else class="user-list">
      <!-- :key wajib diisi dengan identifier unik! -->
      <li 
        v-for="user in users" 
        :key="user.id"
        class="user-card"
      >
        <h3>{{ user.name }}</h3>
        <p>Email: {{ user.email }}</p>
        <p>Perusahaan: {{ user.company.name }}</p>
      </li>
    </ul>
  </div>
</template>

4. Implementasi Skala Produksi: Employee Directory

Untuk memberikan gambaran utuh, di bawah ini adalah file Vue Single File Component (SFC) lengkap. Kode ini tidak hanya melakukan fetch data, tetapi juga menyertakan:

  • Skeleton Loading (UI Modern seperti Facebook/LinkedIn saat memuat data).
  • Computed Properties untuk fitur Pencarian (Search).
  • Styling lengkap menggunakan Tailwind CSS via attribut class.
  • Pemetaan Data (Mapping).
EmployeeDirectory.vue
<!-- 
  ========================================================================
  KOMPONEN: EmployeeDirectory.vue
  DESKRIPSI: Komponen tingkat lanjut untuk mengambil, menampilkan, 
             dan memfilter data pegawai menggunakan Axios & Vue 3.
  AUTHOR: Catatan Kader
  ======================================================================== 
-->

<script setup>
import { ref, computed, onMounted } from 'vue';
import axios from 'axios';

// ---------------------------------------------------------
// STATE MANAGEMENT (REAKTIVITAS)
// ---------------------------------------------------------
const employees = ref([]);
const isLoading = ref(true); // Set true di awal karena data langsung diambil saat mount
const errorMessage = ref(null);

// State untuk fitur pencarian
const searchQuery = ref('');

// ---------------------------------------------------------
// COMPUTED PROPERTIES (LOGIKA TURUNAN)
// ---------------------------------------------------------
/**
 * filteredEmployees memastikan daftar yang tampil hanya yang
 * sesuai dengan kata kunci pencarian pada nama atau email.
 */
const filteredEmployees = computed(() => {
  // Jika query kosong, kembalikan semua data
  if (!searchQuery.value) return employees.value;
  
  const lowerCaseQuery = searchQuery.value.toLowerCase();
  
  return employees.value.filter(emp => 
    emp.name.toLowerCase().includes(lowerCaseQuery) || 
    emp.email.toLowerCase().includes(lowerCaseQuery)
  );
});

// ---------------------------------------------------------
// METHODS (FUNGSI UTAMA)
// ---------------------------------------------------------
/**
 * fetchEmployees()
 * Melakukan HTTP GET Request ke endpoint publik JSONPlaceholder.
 * Menggunakan async/await dan try-catch untuk penanganan error.
 */
const fetchEmployees = async () => {
  // Mulai loading state dan reset error
  isLoading.value = true;
  errorMessage.value = null;

  try {
    // Simulasi delay jaringan agar skeleton loading terlihat (Opsional)
    await new Promise(resolve => setTimeout(resolve, 1500));

    const API_URL = 'https://jsonplaceholder.typicode.com/users';
    const response = await axios.get(API_URL);
    
    // Memasukkan data dari response ke variabel reaktif
    employees.value = response.data;
    
  } catch (error) {
    console.error('Axios Error:', error);
    
    // Memberikan pesan error yang ramah pengguna berdasarkan tipe error
    if (error.response) {
      // Server merespon tapi dengan status error (4xx, 5xx)
      errorMessage.value = `Gagal memuat data (Kode: ${error.response.status}). Server mengalami masalah.`;
    } else if (error.request) {
      // Request terkirim tapi tidak ada respon (Sinyal/Internet mati)
      errorMessage.value = 'Tidak dapat terhubung ke server. Periksa koneksi internet Anda.';
    } else {
      // Kesalahan setup request
      errorMessage.value = 'Terjadi kesalahan tidak terduga dalam sistem.';
    }
  } finally {
    // Pastikan loading selalu dimatikan di akhir
    isLoading.value = false;
  }
};

// ---------------------------------------------------------
// LIFECYCLE HOOKS
// ---------------------------------------------------------
onMounted(() => {
  // Eksekusi fetch segera setelah komponen dimasukkan ke DOM
  fetchEmployees();
});
</script>

<template>
  <div class="min-h-screen bg-slate-50 p-8 font-sans">
    <div class="max-w-6xl mx-auto">
      
      <!-- HEADER & SEARCH SECTION -->
      <div class="flex flex-col md:flex-row justify-between items-center mb-10 gap-6">
        <div>
          <h1 class="text-3xl font-extrabold text-slate-900 tracking-tight">Direktori Pegawai</h1>
          <p class="text-slate-500 mt-2">Manajemen data SDM terpusat.</p>
        </div>
        
        <!-- Input Pencarian -->
        <div class="relative w-full md:w-96">
          <span class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
            <!-- SVG Search Icon -->
            <svg class="h-5 w-5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
            </svg>
          </span>
          <input 
            v-model="searchQuery"
            type="text" 
            class="block w-full pl-10 pr-3 py-3 border border-slate-300 rounded-xl leading-5 bg-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow shadow-sm" 
            placeholder="Cari nama atau email..." 
          />
        </div>
      </div>

      <!-- CONDITIONAL RENDERING -->
      
      <!-- STATE 1: LOADING (SKELETON UI) -->
      <div v-if="isLoading" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        <!-- Mengulang skeleton 6 kali menggunakan v-for dengan angka -->
        <div v-for="i in 6" :key="i" class="bg-white rounded-2xl p-6 border border-slate-200 shadow-sm">
          <div class="flex items-center space-x-4 mb-4">
            <div class="rounded-full bg-slate-200 animate-pulse h-14 w-14"></div>
            <div class="flex-1 space-y-2 py-1">
              <div class="h-4 bg-slate-200 animate-pulse rounded w-3/4"></div>
              <div class="h-3 bg-slate-200 animate-pulse rounded w-1/2"></div>
            </div>
          </div>
          <div class="space-y-3 mt-6">
            <div class="h-3 bg-slate-200 animate-pulse rounded w-full"></div>
            <div class="h-3 bg-slate-200 animate-pulse rounded w-5/6"></div>
          </div>
        </div>
      </div>

      <!-- STATE 2: ERROR -->
      <div v-else-if="errorMessage" class="rounded-xl bg-red-50 p-6 border border-red-200 flex flex-col items-center justify-center text-center py-12">
        <svg class="h-12 w-12 text-red-500 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
        </svg>
        <h3 class="text-lg font-bold text-red-800 mb-2">Sistem Gagal Memuat Data</h3>
        <p class="text-red-600 mb-6">{{ errorMessage }}</p>
        <button @click="fetchEmployees" class="px-6 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors shadow-sm">
          Coba Muat Ulang
        </button>
      </div>

      <!-- STATE 3: SUCCESS (Menampilkan Data) -->
      <div v-else>
        
        <!-- Jika pencarian tidak menemukan hasil -->
        <div v-if="filteredEmployees.length === 0" class="text-center py-20">
          <img src="https://cdn-icons-png.flaticon.com/512/7486/7486744.png" alt="Not found" class="w-32 h-32 mx-auto opacity-50 mb-4">
          <p class="text-slate-500 text-lg">Tidak ada pegawai yang cocok dengan "<span class="font-bold text-slate-800">{{ searchQuery }}</span>"</p>
          <button @click="searchQuery = ''" class="mt-4 text-blue-600 hover:text-blue-800 font-medium underline">Bersihkan pencarian</button>
        </div>

        <!-- Grid Rendering dengan v-for -->
        <div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
          
          <!-- 
            Penggunaan v-for di sini 
            Kita me-looping filteredEmployees, bukan employees 
            sehingga filter search langsung berjalan otomatis.
            :key diisi dengan id unik dari API.
          -->
          <div 
            v-for="employee in filteredEmployees" 
            :key="employee.id"
            class="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300 group"
          >
            <!-- Bagian Avatar dan Nama -->
            <div class="flex items-center space-x-4 mb-5">
              <!-- Avatar generik menggunakan inisial nama -->
              <div class="h-14 w-14 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center font-bold text-xl group-hover:bg-blue-600 group-hover:text-white transition-colors">
                {{ employee.name.charAt(0) }}
              </div>
              <div>
                <h3 class="text-lg font-bold text-slate-900 group-hover:text-blue-600 transition-colors">{{ employee.name }}</h3>
                <p class="text-sm text-slate-500 font-mono">@{{ employee.username }}</p>
              </div>
            </div>
            
            <!-- Divider -->
            <hr class="border-slate-100 mb-4">
            
            <!-- Informasi Detail -->
            <div class="space-y-3 text-sm text-slate-600">
              <!-- Email -->
              <div class="flex items-center gap-3">
                <svg class="w-5 h-5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
                </svg>
                <a :href="`mailto:${employee.email}`" class="hover:text-blue-600 hover:underline truncate">{{ employee.email }}</a>
              </div>
              
              <!-- Telepon -->
              <div class="flex items-center gap-3">
                <svg class="w-5 h-5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
                </svg>
                <span>{{ employee.phone.split(' ')[0] }}</span>
              </div>
              
              <!-- Perusahaan -->
              <div class="flex items-center gap-3">
                <svg class="w-5 h-5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
                </svg>
                <span class="font-semibold text-slate-700">{{ employee.company.name }}</span>
              </div>
            </div>
          </div>

        </div> <!-- End Grid -->
      </div> <!-- End Success State -->

    </div>
  </div>
</template>
Kembali ke Beranda Lanjut ke Modul 3: Data Form & v-model

Catatan Kader

"Menyambungkan UI ke API adalah transisi di mana sebuah 'halaman web' berubah menjadi 'Aplikasi Web'. Jangan pernah lupa menangani state loading dan error, karena koneksi internet pengguna tidak ada yang tahu keadaannya."
- Tech Lead

Selanjutnya

Modul 3: Data Form & v-model

Memanfaatkan two-way data binding dengan v-model. Kita akan menangani event dan mengirim request POST ke server.

Catatan Kader Catatan Kader

Catatan ini dikelola untuk keperluan dokumentasi pribadi, pengembangan kemampuan analisis logika, serta standarisasi implementasi sistem teknologi.

© 2026 Catatan Kader. Deployment Active.